aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin10244 -> 10244 bytes
-rw-r--r--src/ClientUtils.ts660
-rw-r--r--src/ServerUtils.ts27
-rw-r--r--src/Utils.ts871
-rw-r--r--src/client/DocServer.ts241
-rw-r--r--src/client/Network.ts11
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx119
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts90
-rw-r--r--src/client/apis/gpt/GPT.ts10
-rw-r--r--src/client/apis/youtube/YoutubeBox.scss126
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx369
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts24
-rw-r--r--src/client/documents/DocumentTypes.ts6
-rw-r--r--src/client/documents/Documents.ts348
-rw-r--r--src/client/goldenLayout.d.ts3
-rw-r--r--src/client/util/BranchingTrailManager.tsx41
-rw-r--r--src/client/util/CalendarManager.tsx42
-rw-r--r--src/client/util/CaptureManager.tsx19
-rw-r--r--src/client/util/CurrentUserUtils.ts272
-rw-r--r--src/client/util/DictationManager.ts69
-rw-r--r--src/client/util/DocumentManager.ts118
-rw-r--r--src/client/util/DragManager.ts187
-rw-r--r--src/client/util/DropActionTypes.ts9
-rw-r--r--src/client/util/DropConverter.ts77
-rw-r--r--src/client/util/GroupManager.tsx105
-rw-r--r--src/client/util/GroupMemberView.tsx32
-rw-r--r--src/client/util/History.ts38
-rw-r--r--src/client/util/HypothesisUtils.ts6
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts12
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx40
-rw-r--r--src/client/util/InteractionUtils.tsx317
-rw-r--r--src/client/util/KeyCodes.ts2
-rw-r--r--src/client/util/LinkFollower.ts13
-rw-r--r--src/client/util/LinkManager.ts77
-rw-r--r--src/client/util/PingManager.ts6
-rw-r--r--src/client/util/RTFMarkup.tsx25
-rw-r--r--src/client/util/ReplayMovements.ts22
-rw-r--r--src/client/util/ScriptManager.ts10
-rw-r--r--src/client/util/Scripting.ts56
-rw-r--r--src/client/util/ScriptingGlobals.ts48
-rw-r--r--src/client/util/SearchUtil.ts26
-rw-r--r--src/client/util/SelectionManager.ts16
-rw-r--r--src/client/util/SerializationHelper.ts33
-rw-r--r--src/client/util/ServerStats.tsx45
-rw-r--r--src/client/util/SettingsManager.tsx403
-rw-r--r--src/client/util/SharingManager.tsx733
-rw-r--r--src/client/util/SnappingManager.ts34
-rw-r--r--src/client/util/TrackMovements.ts24
-rw-r--r--src/client/util/Transform.ts14
-rw-r--r--src/client/util/TypedEvent.ts18
-rw-r--r--src/client/util/UndoManager.ts99
-rw-r--r--src/client/util/bezierFit.ts401
-rw-r--r--src/client/util/reportManager/ReportManager.tsx67
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx131
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts1
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts17
-rw-r--r--src/client/util/request-image-size.ts26
-rw-r--r--src/client/views/AntimodeMenu.tsx18
-rw-r--r--src/client/views/ComponentDecorations.tsx3
-rw-r--r--src/client/views/ContextMenu.tsx49
-rw-r--r--src/client/views/ContextMenuItem.tsx42
-rw-r--r--src/client/views/DashboardView.tsx103
-rw-r--r--src/client/views/DictationOverlay.tsx55
-rw-r--r--src/client/views/DocComponent.tsx84
-rw-r--r--src/client/views/DocumentButtonBar.tsx134
-rw-r--r--src/client/views/DocumentDecorations.tsx122
-rw-r--r--src/client/views/EditableView.tsx23
-rw-r--r--src/client/views/FieldsDropdown.tsx16
-rw-r--r--src/client/views/FilterPanel.tsx165
-rw-r--r--src/client/views/GestureOverlay.tsx214
-rw-r--r--src/client/views/GlobalKeyHandler.ts127
-rw-r--r--src/client/views/InkControlPtHandles.tsx35
-rw-r--r--src/client/views/InkStrokeProperties.ts77
-rw-r--r--src/client/views/InkTangentHandles.tsx16
-rw-r--r--src/client/views/InkingStroke.tsx130
-rw-r--r--src/client/views/KeyphraseQueryView.tsx26
-rw-r--r--src/client/views/LightboxView.tsx91
-rw-r--r--src/client/views/Main.tsx4
-rw-r--r--src/client/views/MainView.tsx170
-rw-r--r--src/client/views/MainViewModal.tsx9
-rw-r--r--src/client/views/MarqueeAnnotator.tsx46
-rw-r--r--src/client/views/MetadataEntryMenu.tsx197
-rw-r--r--src/client/views/ObservableReactComponent.tsx7
-rw-r--r--src/client/views/OverlayView.tsx44
-rw-r--r--src/client/views/PreviewCursor.tsx18
-rw-r--r--src/client/views/PropertiesButtons.tsx188
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx1
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx11
-rw-r--r--src/client/views/PropertiesSection.tsx60
-rw-r--r--src/client/views/PropertiesView.tsx1087
-rw-r--r--src/client/views/ScriptBox.tsx39
-rw-r--r--src/client/views/ScriptingRepl.tsx191
-rw-r--r--src/client/views/SidebarAnnos.tsx24
-rw-r--r--src/client/views/StyleProvider.tsx186
-rw-r--r--src/client/views/TemplateMenu.tsx70
-rw-r--r--src/client/views/Touchable.tsx213
-rw-r--r--src/client/views/UndoStack.tsx19
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx64
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.tsx25
-rw-r--r--src/client/views/animationtimeline/TimelineOverview.tsx16
-rw-r--r--src/client/views/animationtimeline/Track.tsx2
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx33
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx97
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx12
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx162
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx99
-rw-r--r--src/client/views/collections/CollectionMenu.tsx130
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx100
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx41
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx5
-rw-r--r--src/client/views/collections/CollectionPileView.tsx18
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx155
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx79
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx82
-rw-r--r--src/client/views/collections/CollectionSubView.tsx198
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx37
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx106
-rw-r--r--src/client/views/collections/CollectionView.tsx75
-rw-r--r--src/client/views/collections/KeyRestrictionRow.tsx32
-rw-r--r--src/client/views/collections/TabDocView.tsx390
-rw-r--r--src/client/views/collections/TreeView.tsx351
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx31
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx60
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx62
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx20
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx76
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx412
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx60
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx55
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx69
-rw-r--r--src/client/views/collections/collectionLinear/index.ts2
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx57
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx24
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx24
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx2
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx3
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx26
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx205
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx21
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx26
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx45
-rw-r--r--src/client/views/global/globalEnums.tsx52
-rw-r--r--src/client/views/global/globalScripts.ts146
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx27
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx48
-rw-r--r--src/client/views/linking/LinkPopup.tsx22
-rw-r--r--src/client/views/linking/LinkRelationshipSearch.tsx63
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx3
-rw-r--r--src/client/views/nodes/AudioBox.tsx115
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx53
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx93
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx151
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx65
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx246
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx145
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx213
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx58
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts29
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx40
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx63
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx49
-rw-r--r--src/client/views/nodes/DocumentView.tsx284
-rw-r--r--src/client/views/nodes/EquationBox.tsx13
-rw-r--r--src/client/views/nodes/FaceRectangle.tsx10
-rw-r--r--src/client/views/nodes/FieldView.tsx21
-rw-r--r--src/client/views/nodes/FontIconBox/ButtonInterface.ts2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx61
-rw-r--r--src/client/views/nodes/FontIconBox/TrailsIcon.tsx86
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx10
-rw-r--r--src/client/views/nodes/ImageBox.tsx124
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx71
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx16
-rw-r--r--src/client/views/nodes/LabelBox.tsx35
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx23
-rw-r--r--src/client/views/nodes/LinkBox.tsx86
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx33
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx67
-rw-r--r--src/client/views/nodes/LoadingBox.tsx4
-rw-r--r--src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx39
-rw-r--r--src/client/views/nodes/MapBox/AnimationUtility.ts57
-rw-r--r--src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx14
-rw-r--r--src/client/views/nodes/MapBox/GeocoderControl.tsx7
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx102
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx36
-rw-r--r--src/client/views/nodes/MapBox/MapboxApiUtility.ts76
-rw-r--r--src/client/views/nodes/MapBox/MarkerIcons.tsx3
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx131
-rw-r--r--src/client/views/nodes/PDFBox.scss2
-rw-r--r--src/client/views/nodes/PDFBox.tsx143
-rw-r--r--src/client/views/nodes/RadialMenu.tsx140
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx4
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.tsx205
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx63
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx43
-rw-r--r--src/client/views/nodes/RecordingBox/index.ts4
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx46
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx164
-rw-r--r--src/client/views/nodes/SliderBox-components.tsx114
-rw-r--r--src/client/views/nodes/VideoBox.tsx164
-rw-r--r--src/client/views/nodes/WebBox.tsx189
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.tsx8
-rw-r--r--src/client/views/nodes/audio/WaveCanvas.tsx12
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx9
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx159
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx122
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx318
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx3
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx93
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx526
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx33
-rw-r--r--src/client/views/nodes/formattedText/OrderedListView.tsx9
-rw-r--r--src/client/views/nodes/formattedText/ParagraphNodeSpec.ts101
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts121
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx264
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts138
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx24
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts43
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts37
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx110
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx10
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts8
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts36
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts18
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx5
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx788
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx97
-rw-r--r--src/client/views/nodes/trails/index.ts6
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx42
-rw-r--r--src/client/views/pdf/Annotation.tsx91
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx96
-rw-r--r--src/client/views/pdf/PDFViewer.tsx73
-rw-r--r--src/client/views/search/SearchBox.tsx255
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.tsx2
-rw-r--r--src/client/views/selectedDoc/index.ts2
-rw-r--r--src/client/views/topbar/TopBar.tsx30
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.scss82
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx76
-rw-r--r--src/client/views/webcam/WebCamLogic.js292
-rw-r--r--src/debug/Repl.tsx18
-rw-r--r--src/debug/Test.tsx3
-rw-r--r--src/debug/Viewer.tsx46
-rw-r--r--src/extensions/Extensions.ts9
-rw-r--r--src/extensions/ExtensionsTypings.ts (renamed from src/extensions/General/ExtensionsTypings.ts)3
-rw-r--r--src/extensions/Extensions_Array.ts (renamed from src/extensions/ArrayExtensions.ts)10
-rw-r--r--src/extensions/Extensions_String.ts (renamed from src/extensions/StringExtensions.ts)9
-rw-r--r--src/extensions/General/Extensions.ts9
-rw-r--r--src/fields/DateField.ts5
-rw-r--r--src/fields/Doc.ts423
-rw-r--r--src/fields/HtmlField.ts4
-rw-r--r--src/fields/IconField.ts2
-rw-r--r--src/fields/InkField.ts11
-rw-r--r--src/fields/List.ts97
-rw-r--r--src/fields/ObjectField.ts26
-rw-r--r--src/fields/Proxy.ts17
-rw-r--r--src/fields/RefField.ts4
-rw-r--r--src/fields/RichTextUtils.ts17
-rw-r--r--src/fields/Schema.ts25
-rw-r--r--src/fields/SchemaHeaderField.ts25
-rw-r--r--src/fields/ScriptField.ts96
-rw-r--r--src/fields/Types.ts86
-rw-r--r--src/fields/URLField.ts40
-rw-r--r--src/fields/documentSchemas.ts3
-rw-r--r--src/fields/util.ts210
-rw-r--r--src/mobile/ImageUpload.tsx100
-rw-r--r--src/mobile/MobileInkOverlay.tsx6
-rw-r--r--src/mobile/MobileInterface.tsx4
-rw-r--r--src/mobile/MobileMain.tsx2
-rw-r--r--src/pen-gestures/GestureTypes.ts16
-rw-r--r--src/pen-gestures/GestureUtils.ts26
-rw-r--r--src/pen-gestures/ndollar.ts246
-rw-r--r--src/server/ActionUtilities.ts136
-rw-r--r--src/server/ApiManagers/ApiManager.ts4
-rw-r--r--src/server/ApiManagers/DataVizManager.ts15
-rw-r--r--src/server/ApiManagers/DeleteManager.ts14
-rw-r--r--src/server/ApiManagers/DownloadManager.ts385
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts39
-rw-r--r--src/server/ApiManagers/MongoStore.js413
-rw-r--r--src/server/ApiManagers/SearchManager.ts17
-rw-r--r--src/server/ApiManagers/SessionManager.ts75
-rw-r--r--src/server/ApiManagers/UploadManager.ts346
-rw-r--r--src/server/ApiManagers/UserManager.ts60
-rw-r--r--src/server/ApiManagers/UtilManager.ts31
-rw-r--r--src/server/Client.ts8
-rw-r--r--src/server/DashSession/DashSessionAgent.ts28
-rw-r--r--src/server/DashSession/Session/agents/applied_session_agent.ts23
-rw-r--r--src/server/DashSession/Session/agents/monitor.ts48
-rw-r--r--src/server/DashSession/Session/agents/process_message_router.ts12
-rw-r--r--src/server/DashSession/Session/agents/promisified_ipc_manager.ts51
-rw-r--r--src/server/DashSession/Session/agents/server_worker.ts46
-rw-r--r--src/server/DashSession/Session/utilities/repl.ts76
-rw-r--r--src/server/DashSession/Session/utilities/session_config.ts104
-rw-r--r--src/server/DashSession/Session/utilities/utilities.ts43
-rw-r--r--src/server/DashStats.ts244
-rw-r--r--src/server/DashUploadUtils.ts746
-rw-r--r--src/server/GarbageCollector.ts70
-rw-r--r--src/server/MemoryDatabase.ts37
-rw-r--r--src/server/Message.ts91
-rw-r--r--src/server/PdfTypes.ts20
-rw-r--r--src/server/ProcessFactory.ts54
-rw-r--r--src/server/RouteManager.ts92
-rw-r--r--src/server/RouteSubscriber.ts5
-rw-r--r--src/server/SharedMediaTypes.ts31
-rw-r--r--src/server/SocketData.ts35
-rw-r--r--src/server/apis/google/CredentialsLoader.ts24
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts16
-rw-r--r--src/server/apis/google/SharedTypes.ts13
-rw-r--r--src/server/apis/youtube/youtubeApiSample.d.ts2
-rw-r--r--src/server/authentication/AuthenticationManager.ts109
-rw-r--r--src/server/authentication/DashUserModel.ts18
-rw-r--r--src/server/authentication/Passport.ts25
-rw-r--r--src/server/database.ts167
-rw-r--r--src/server/index.ts62
-rw-r--r--src/server/remapUrl.ts69
-rw-r--r--src/server/server_Initialization.ts251
-rw-r--r--src/server/updateProtos.ts6
-rw-r--r--src/server/websocket.ts356
320 files changed, 14865 insertions, 14127 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index f8d745dbf..75cff7b55 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
new file mode 100644
index 000000000..402f6613a
--- /dev/null
+++ b/src/ClientUtils.ts
@@ -0,0 +1,660 @@
+import * as Color from 'color';
+import * as React from 'react';
+import { ColorResult } from 'react-color';
+import * as rp from 'request-promise';
+import { numberRange, decimalToHexString } from './Utils';
+import { CollectionViewType, DocumentType } from './client/documents/DocumentTypes';
+import { Colors } from './client/views/global/globalEnums';
+
+export function DashColor(color: string) {
+ try {
+ return color ? Color(color.toLowerCase()) : Color('transparent');
+ } catch (e) {
+ if (color.includes('gradient')) console.log("using color 'white' in place of :" + color);
+ else console.log('COLOR error:', e);
+ return Color('white');
+ }
+}
+
+export function lightOrDark(color: any) {
+ if (color === 'transparent' || !color) return Colors.BLACK;
+ if (color.startsWith?.('linear')) return Colors.BLACK;
+ if (DashColor(color).isLight()) return Colors.BLACK;
+ return Colors.WHITE;
+}
+
+export function returnTransparent() {
+ return 'transparent';
+}
+
+export function returnTrue() {
+ return true;
+}
+
+export function returnIgnore(): 'ignore' {
+ return 'ignore';
+}
+export function returnAlways(): 'always' {
+ return 'always';
+}
+export function returnNever(): 'never' {
+ return 'never';
+}
+
+export function returnDefault(): 'default' {
+ return 'default';
+}
+
+export function return18() {
+ return 18;
+}
+
+export function returnFalse() {
+ return false;
+}
+
+export function returnAll(): 'all' {
+ return 'all';
+}
+
+export function returnNone(): 'none' {
+ return 'none';
+}
+
+export function returnVal(val1?: number, val2?: number) {
+ return val1 || (val2 !== undefined ? val2 : 0);
+}
+
+export function returnOne() {
+ return 1;
+}
+
+export function returnZero() {
+ return 0;
+}
+
+export function returnEmptyString() {
+ return '';
+}
+
+export function returnEmptyFilter() {
+ return [] as string[];
+}
+
+export function returnEmptyDoclist() {
+ return [] as any[];
+}
+
+export namespace ClientUtils {
+ export const CLICK_TIME = 300;
+ export const DRAG_THRESHOLD = 4;
+ export const SNAP_THRESHOLD = 10;
+ let _currentUserEmail: string = '';
+ export function CurrentUserEmail() {
+ return _currentUserEmail;
+ }
+ export function SetCurrentUserEmail(email: string) {
+ _currentUserEmail = email;
+ }
+ export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) {
+ return Date.now() - downTime < ClientUtils.CLICK_TIME && Math.abs(x - downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(y - downY) < ClientUtils.DRAG_THRESHOLD;
+ }
+
+ export function cleanDocumentTypeExt(type: DocumentType) {
+ switch (type) {
+ case DocumentType.PDF: return 'PDF';
+ case DocumentType.IMG: return 'Img';
+ case DocumentType.AUDIO: return 'Aud';
+ case DocumentType.COL: return 'Col';
+ case DocumentType.RTF: return 'Rtf';
+ default: return type.charAt(0).toUpperCase() + type.substring(1,3);
+ } // prettier-ignore
+ }
+ export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) {
+ switch (type) {
+ case DocumentType.PDF: return 'PDF';
+ case DocumentType.IMG: return 'Image';
+ case DocumentType.AUDIO: return 'Audio';
+ case DocumentType.COL: return 'Collection:'+colType;
+ case DocumentType.RTF: return 'Text';
+ default: return type.charAt(0).toUpperCase() + type.slice(1);
+ } // prettier-ignore
+ }
+
+ export function readUploadedFileAsText(inputFile: File) {
+ // eslint-disable-next-line no-undef
+ const temporaryFileReader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ temporaryFileReader.onerror = () => {
+ temporaryFileReader.abort();
+ reject(new DOMException('Problem parsing input file.'));
+ };
+
+ temporaryFileReader.onload = () => {
+ resolve(temporaryFileReader.result);
+ };
+ temporaryFileReader.readAsText(inputFile);
+ });
+ }
+
+ /**
+ * Uploads an image buffer to the server and stores with specified filename. by default the image
+ * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large)
+ * @param imageUri the bytes of the image
+ * @param returnedFilename the base filename to store the image on the server
+ * @param nosuffix optionally suppress creating multiple resolution images
+ */
+ export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename: string | undefined = undefined) {
+ try {
+ const posting = ClientUtils.prepend('/uploadURI');
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename,
+ nosuffix,
+ replaceRootFilename,
+ },
+ json: true,
+ });
+ return returnedUri;
+ } catch (e) {
+ console.log('ConvertDataURI :' + e);
+ }
+ return undefined;
+ }
+
+ export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } {
+ if (!ele) {
+ return { scale: 1, translateX: 1, translateY: 1 };
+ }
+ const rect = ele.getBoundingClientRect();
+ const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
+ const translateX = rect.left;
+ const translateY = rect.top;
+
+ return { scale, translateX, translateY };
+ }
+
+ /**
+ * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the
+ * requested extension
+ * @param extension the specified sub-path to append to the window origin
+ */
+ export function prepend(extension: string): string {
+ return window.location.origin + extension;
+ }
+ export function fileUrl(filename: string): string {
+ return prepend(`/files/${filename}`);
+ }
+
+ export function shareUrl(documentId: string): string {
+ return prepend(`/doc/${documentId}?sharing=true`);
+ }
+
+ export function CorsProxy(url: string): string {
+ return prepend('/corsProxy/') + encodeURIComponent(url);
+ }
+
+ export function CopyText(text: string) {
+ navigator.clipboard.writeText(text);
+ }
+
+ export function colorString(color: ColorResult) {
+ 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) {
+ const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
+ const r = rm ? Number(rm[1]) : 0;
+ const gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/);
+ const g = gm ? Number(gm[1]) : 0;
+ const bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/);
+ const b = bm ? Number(bm[1]) : 0;
+ const am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/);
+ const a = am ? Number(am[1]) : 1;
+ return { r: r, g: g, b: b, a: a };
+ }
+
+ const isTransparentFunctionHack = 'isTransparent(__value__)';
+ export const noRecursionHack = '__noRecursion';
+
+ // special case filters
+ export const noDragDocsFilter = 'noDragDocs::any::check';
+ export const TransparentBackgroundFilter = `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 const OpaqueBackgroundFilter = `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 IsRecursiveFilter(val: string) {
+ return !val.includes(noRecursionHack);
+ }
+ export function HasFunctionFilter(val: string) {
+ if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1;
+ // add other function filters here...
+ return undefined;
+ }
+
+ export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
+ return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
+ }
+
+ export function HSLtoRGB(h: number, s: number, l: number) {
+ // Must be fractions of 1
+ // s /= 100;
+ // l /= 100;
+
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
+ const m = l - c / 2;
+ let r = 0;
+ let g = 0;
+ let b = 0;
+ if (h >= 0 && h < 60) {
+ r = c;
+ g = x;
+ b = 0;
+ } else if (h >= 60 && h < 120) {
+ r = x;
+ g = c;
+ b = 0;
+ } else if (h >= 120 && h < 180) {
+ r = 0;
+ g = c;
+ b = x;
+ } else if (h >= 180 && h < 240) {
+ r = 0;
+ g = x;
+ b = c;
+ } else if (h >= 240 && h < 300) {
+ r = x;
+ g = 0;
+ b = c;
+ } else if (h >= 300 && h < 360) {
+ r = c;
+ g = 0;
+ b = x;
+ }
+ r = Math.round((r + m) * 255);
+ g = Math.round((g + m) * 255);
+ b = Math.round((b + m) * 255);
+ return { r: r, g: g, b: b };
+ }
+
+ export function RGBToHSL(red: number, green: number, blue: number) {
+ // Make r, g, and b fractions of 1
+ const r = red / 255;
+ const g = green / 255;
+ const b = blue / 255;
+
+ // Find greatest and smallest channel values
+ const cmin = Math.min(r, g, b);
+ const cmax = Math.max(r, g, b);
+ const delta = cmax - cmin;
+ let h = 0;
+ let s = 0;
+ let l = 0;
+ // Calculate hue
+
+ // No difference
+ if (delta === 0) h = 0;
+ // Red is max
+ else if (cmax === r) h = ((g - b) / delta) % 6;
+ // Green is max
+ else if (cmax === g) h = (b - r) / delta + 2;
+ // Blue is max
+ else h = (r - g) / delta + 4;
+
+ h = Math.round(h * 60);
+
+ // Make negative hues positive behind 360°
+ if (h < 0) h += 360; // Calculate lightness
+
+ l = (cmax + cmin) / 2;
+
+ // Calculate saturation
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
+
+ // Multiply l and s by 100
+ // s = +(s * 100).toFixed(1);
+ // l = +(l * 100).toFixed(1);
+
+ return { h: h, s: s, l: l };
+ }
+
+ export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) {
+ if (!targetHgt) return targetY; // if there's no height, then assume that
+ if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
+ return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
+ }
+ if (scrollTop >= Math.max(0, targetY - minSpacing)) {
+ return Math.max(0, Math.floor(targetY - minSpacing));
+ }
+ return undefined;
+ }
+
+ export function GetClipboardText(): string {
+ const textArea = document.createElement('textarea');
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+
+ try {
+ document.execCommand('paste');
+ } catch (err) {
+ /* empty */
+ }
+
+ const val = textArea.value;
+ document.body.removeChild(textArea);
+ return val;
+ }
+}
+
+export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
+ const omit: any = { ...obj };
+ const extract: any = {};
+ keys.forEach(key => {
+ extract[key] = omit[key];
+ delete omit[key];
+ });
+ pattern &&
+ Array.from(Object.keys(omit))
+ .filter(key => key.match(pattern))
+ .forEach(key => {
+ extract[key] = omit[key];
+ delete omit[key];
+ });
+ addKeyFunc?.(omit);
+ return { omit, extract };
+}
+
+export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
+ const dup: any = {};
+ keys.forEach(key => {
+ dup[key] = obj[key];
+ });
+ addKeyFunc && addKeyFunc(dup);
+ return dup;
+}
+
+export function incrementTitleCopy(title: string) {
+ const numstr = title.match(/.*(\{([0-9]*)\})+/);
+ const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`;
+ return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr;
+}
+
+const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => {
+ if (transition === 'linear') {
+ const newCurrentTime = currentTime / duration; // currentTime / (duration / 2);
+ return start + newCurrentTime * change;
+ }
+
+ let newCurrentTime = currentTime / (duration / 2);
+ if (newCurrentTime < 1) {
+ return (change / 2) * newCurrentTime * newCurrentTime + start;
+ }
+
+ newCurrentTime -= 1;
+ return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
+};
+
+export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) {
+ stopper?.();
+ const elements = element instanceof HTMLElement ? [element] : element;
+ const starts = elements.map(element => element.scrollTop);
+ const startDate = new Date().getTime();
+ let _stop = false;
+ const stop = () => {
+ _stop = true;
+ };
+ const animateScroll = () => {
+ const currentDate = new Date().getTime();
+ const currentTime = currentDate - startDate;
+ const setScrollTop = (element: HTMLElement, value: number) => {
+ element.scrollTop = value;
+ };
+ if (!_stop) {
+ if (currentTime < duration) {
+ elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
+ requestAnimationFrame(animateScroll);
+ } else {
+ elements.forEach(element => setScrollTop(element, to));
+ }
+ }
+ };
+ animateScroll();
+ return stop;
+}
+
+export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
+ const elements = element instanceof HTMLElement ? [element] : element;
+ const starts = elements.map(element => element.scrollLeft);
+ const startDate = new Date().getTime();
+
+ const animateScroll = () => {
+ const currentDate = new Date().getTime();
+ const currentTime = currentDate - startDate;
+ elements.forEach((element, i) => {
+ element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration);
+ });
+
+ if (currentTime < duration) {
+ requestAnimationFrame(animateScroll);
+ } else {
+ elements.forEach(element => {
+ element.scrollLeft = to;
+ });
+ }
+ };
+ animateScroll();
+}
+
+export function addStyleSheet(styleType: string = 'text/css') {
+ const style = document.createElement('style');
+ style.type = styleType;
+ const sheets = document.head.appendChild(style);
+ return (sheets as any).sheet;
+}
+export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
+ const propText =
+ typeof css === 'string'
+ ? css
+ : Object.keys(css)
+ .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]))
+ .join(';');
+ return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
+}
+export function removeStyleSheetRule(sheet: any, rule: number) {
+ if (sheet.rules.length) {
+ sheet.removeRule(rule);
+ return true;
+ }
+ return false;
+}
+export function clearStyleSheetRules(sheet: any) {
+ if (sheet.rules.length) {
+ numberRange(sheet.rules.length).map(() => sheet.removeRule(0));
+ return true;
+ }
+ return false;
+}
+
+export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) {
+ if (!element) return;
+ ['pointerdown', 'pointerup'].forEach(event => {
+ const me = new PointerEvent(event, {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ button: 2,
+ pointerType: 'mouse',
+ clientX: x,
+ clientY: y,
+ screenX: sx,
+ screenY: sy,
+ });
+ (me as any).dash = true;
+ element.dispatchEvent(me);
+ });
+
+ if (rightClick) {
+ const me = new MouseEvent('contextmenu', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ button: 2,
+ clientX: x,
+ clientY: y,
+ movementX: 0,
+ movementY: 0,
+ screenX: sx,
+ screenY: sy,
+ });
+ (me as any).dash = true;
+ element.dispatchEvent(me);
+ }
+}
+
+export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
+ if (elem.tagName === 'INPUT') return 'input';
+ if (elem.tagName === 'TEXTAREA') return 'textarea';
+ if (elem.nodeType === elem.TEXT_NODE) {
+ const range = elem.ownerDocument.createRange();
+ range.selectNodeContents(elem);
+ let currentPos = 0;
+ const endPos = range.endOffset;
+ while (currentPos + 1 <= endPos) {
+ range.setStart(elem, currentPos);
+ range.setEnd(elem, currentPos + 1);
+ const rangeRect = range.getBoundingClientRect();
+ if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
+ range.expand?.('word'); // doesn't exist in firefox
+ const ret = range.toString();
+ range.detach();
+ return ret;
+ }
+ currentPos += 1;
+ }
+ } else {
+ Array.from(elem.childNodes).forEach((childNode: any) => {
+ const range = childNode.ownerDocument.createRange();
+ range.selectNodeContents(childNode);
+ const rangeRect = range.getBoundingClientRect();
+ if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
+ range.detach();
+ const word = getWordAtPoint(childNode, x, y);
+ if (word) return word;
+ } else {
+ range.detach();
+ }
+ return undefined;
+ });
+ }
+ return undefined;
+}
+
+export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) {
+ let entered = false;
+ for (let child = target; !entered && child; child = child.parentElement) {
+ entered = child === ele;
+ }
+ return entered;
+}
+
+export function StopEvent(e: React.PointerEvent | React.MouseEvent | React.KeyboardEvent) {
+ e.stopPropagation();
+ e.preventDefault();
+}
+
+export function setupMoveUpEvents(
+ target: object,
+ e: React.PointerEvent,
+ moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
+ upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any,
+ clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
+ // eslint-disable-next-line default-param-last
+ stopPropagation: boolean = true,
+ // eslint-disable-next-line default-param-last
+ stopMovePropagation: boolean = true,
+ noDoubleTapTimeout?: () => void
+) {
+ const targetAny = target as any;
+ const doubleTapTimeout = 300;
+ targetAny._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout;
+ targetAny._lastTap = Date.now();
+ targetAny._downX = targetAny._lastX = e.clientX;
+ targetAny._downY = targetAny._lastY = e.clientY;
+ targetAny._noClick = false;
+ let moving = false;
+
+ const _moveEvent = (e: PointerEvent): void => {
+ if (moving || Math.abs(e.clientX - (target as any)._downX) > ClientUtils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > ClientUtils.DRAG_THRESHOLD) {
+ moving = true;
+ if ((target as any)._doubleTime) {
+ clearTimeout((target as any)._doubleTime);
+ targetAny._doubleTime = undefined;
+ }
+ if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
+ document.removeEventListener('pointermove', _moveEvent);
+ // eslint-disable-next-line no-use-before-define
+ document.removeEventListener('pointerup', _upEvent);
+ }
+ }
+ targetAny._lastX = e.clientX;
+ targetAny._lastY = e.clientY;
+ stopMovePropagation && e.stopPropagation();
+ };
+ const _upEvent = (e: PointerEvent): void => {
+ const isClick = Math.abs(e.clientX - targetAny._downX) < 4 && Math.abs(e.clientY - targetAny._downY) < 4;
+ upEvent(e, [e.clientX - targetAny._downX, e.clientY - targetAny._downY], isClick);
+ if (isClick) {
+ if (!targetAny._doubleTime && noDoubleTapTimeout) {
+ targetAny._doubleTime = setTimeout(() => {
+ noDoubleTapTimeout?.();
+ targetAny._doubleTime = undefined;
+ }, doubleTapTimeout);
+ }
+ if (targetAny._doubleTime && targetAny._doubleTap) {
+ clearTimeout(targetAny._doubleTime);
+ targetAny._doubleTime = undefined;
+ }
+ targetAny._noClick = clickEvent(e, targetAny._doubleTap);
+ }
+ document.removeEventListener('pointermove', _moveEvent);
+ document.removeEventListener('pointerup', _upEvent, true);
+ };
+ const _clickEvent = (e: MouseEvent): void => {
+ if ((target as any)._noClick) e.stopPropagation();
+ document.removeEventListener('click', _clickEvent, true);
+ };
+ if (stopPropagation) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ document.addEventListener('pointermove', _moveEvent);
+ document.addEventListener('pointerup', _upEvent, true);
+ document.addEventListener('click', _clickEvent, true);
+}
+
+export function DivHeight(ele: HTMLElement): number {
+ return Number(getComputedStyle(ele).height.replace('px', ''));
+}
+export function DivWidth(ele: HTMLElement): number {
+ return Number(getComputedStyle(ele).width.replace('px', ''));
+}
+
+export function dateRangeStrToDates(dateStr: string) {
+ // dateStr in yyyy-mm-dd format
+ const dateRangeParts = dateStr.split('|'); // splits into from and to date
+ const fromParts = dateRangeParts[0].split('-');
+ const toParts = dateRangeParts[1].split('-');
+
+ const fromYear = parseInt(fromParts[0]);
+ const fromMonth = parseInt(fromParts[1]) - 1;
+ const fromDay = parseInt(fromParts[2]);
+
+ const toYear = parseInt(toParts[0]);
+ const toMonth = parseInt(toParts[1]) - 1;
+ const toDay = parseInt(toParts[2]);
+
+ return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
+}
diff --git a/src/ServerUtils.ts b/src/ServerUtils.ts
new file mode 100644
index 000000000..ade4ca35d
--- /dev/null
+++ b/src/ServerUtils.ts
@@ -0,0 +1,27 @@
+import { Socket } from 'socket.io';
+import { Message } from './server/Message';
+import { Utils } from './Utils';
+
+export namespace ServerUtils {
+ export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
+ Utils.log('Emit', message.Name, args, false);
+ socket.emit(message.Message, args);
+ }
+
+ export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
+ socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
+ }
+
+ export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
+ socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
+ Utils.log('S receiving', message.Name, arg, true);
+ handler([arg, Utils.loggingCallback('S sending', fn, message.Name)]);
+ });
+ }
+ export type RoomHandler = (socket: Socket, room: string) => any;
+ export type UsedSockets = Socket;
+ export type RoomMessage = 'create or join' | 'created' | 'joined';
+ export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
+ socket.on(message, (room: any) => handler(socket, room));
+ }
+}
diff --git a/src/Utils.ts b/src/Utils.ts
index 291d7c799..c87ef052c 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,52 +1,12 @@
-import * as Color from 'color';
-import { ColorResult } from 'react-color';
-import * as rp from 'request-promise';
-import { Socket } from 'socket.io';
import * as uuid from 'uuid';
-import { DocumentType } from './client/documents/DocumentTypes';
-import { Colors } from './client/views/global/globalEnums';
-import { DocumentView } from './client/views/nodes/DocumentView';
-import { Message } from './server/Message';
+export function clamp(n: number, lower: number, upper: number) {
+ return Math.max(lower, Math.min(upper, n));
+}
export namespace Utils {
- export let CLICK_TIME = 300;
- export let DRAG_THRESHOLD = 4;
- export let SNAP_THRESHOLD = 10;
- export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) {
- 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();
-
- return new Promise((resolve, reject) => {
- temporaryFileReader.onerror = () => {
- temporaryFileReader.abort();
- reject(new DOMException('Problem parsing input file.'));
- };
-
- temporaryFileReader.onload = () => {
- resolve(temporaryFileReader.result);
- };
- temporaryFileReader.readAsText(inputFile);
- });
+ export function GuestID() {
+ return '__guest__';
}
-
export function GenerateGuid(): string {
return uuid.v4();
}
@@ -55,399 +15,138 @@ export namespace Utils {
return uuid.v5(seed, uuid.v5.URL);
}
- export function GuestID() {
- return '__guest__';
- }
+ export const loggingEnabled: Boolean = false;
+ export const logFilter: number | undefined = undefined;
- /**
- * Uploads an image buffer to the server and stores with specified filename. by default the image
- * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large)
- * @param imageUri the bytes of the image
- * @param returnedFilename the base filename to store the image on the server
- * @param nosuffix optionally suppress creating multiple resolution images
- */
- export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) {
- try {
- const posting = Utils.prepend('/uploadURI');
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename,
- nosuffix,
- replaceRootFilename,
- },
- json: true,
- });
- return returnedUri;
- } catch (e) {
- console.log('ConvertDataURI :' + e);
+ export function log(prefixIn: string, messageName: string, messageIn: any, receiving: boolean) {
+ let prefix = prefixIn;
+ let message = messageIn;
+ if (!loggingEnabled) {
+ return;
}
- }
-
- export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } {
- if (!ele) {
- return { scale: 1, translateX: 1, translateY: 1 };
+ message = message || {};
+ if (logFilter !== undefined && logFilter !== message.type) {
+ return;
}
- const rect = ele.getBoundingClientRect();
- const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
- const translateX = rect.left;
- const translateY = rect.top;
+ const idString = (message.id || '').padStart(36, ' ');
+ prefix = prefix.padEnd(16, ' ');
+ console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
+ }
- return { scale, translateX, translateY };
+ export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
+ return (args: any) => {
+ log(prefix, messageName, args, true);
+ func(args);
+ };
}
export function TraceConsoleLog() {
- ['log', 'warn'].forEach(function (method) {
+ ['log', 'warn'].forEach(method => {
const old = (console as any)[method];
- (console as any)[method] = function () {
+ (console as any)[method] = function (...args: any[]) {
let stack = new Error('').stack?.split(/\n/);
// Chrome includes a single "Error" line, FF doesn't.
if (stack && stack[0].indexOf('Error') === 0) {
stack = stack.slice(1);
}
const message = (stack?.[1] || 'Stack undefined!').trim();
- const args = ([] as any[]).slice.apply(arguments).concat([message]);
- return old.apply(console, args);
+ const newArgs = args.slice().concat([message]);
+ return old.apply(console, newArgs);
};
});
}
- /**
- * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the
- * requested extension
- * @param extension the specified sub-path to append to the window origin
- */
- export function prepend(extension: string): string {
- return window.location.origin + extension;
- }
- export function fileUrl(filename: string): string {
- return prepend(`/files/${filename}`);
- }
-
- export function shareUrl(documentId: string): string {
- return prepend(`/doc/${documentId}?sharing=true`);
- }
-
- export function CorsProxy(url: string): string {
- return prepend('/corsProxy/') + encodeURIComponent(url);
- }
-
- export function CopyText(text: string) {
- navigator.clipboard.writeText(text);
- }
-
- export function decimalToHexString(number: number) {
- if (number < 0) {
- number = 0xffffffff + number + 1;
- }
- return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
- }
-
- export function colorString(color: ColorResult) {
- 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) {
- const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
- const r = rm ? Number(rm[1]) : 0;
- const gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/);
- const g = gm ? Number(gm[1]) : 0;
- const bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/);
- const b = bm ? Number(bm[1]) : 0;
- const am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/);
- const a = am ? Number(am[1]) : 1;
- return { r: r, g: g, b: b, a: a };
- }
-
- const isTransparentFunctionHack = 'isTransparent(__value__)';
- export const noRecursionHack = '__noRecursion';
-
- // special case filters
- export const noDragDocsFilter = 'noDragDocs::any::check';
- export const TransparentBackgroundFilter = `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 const OpaqueBackgroundFilter = `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 IsRecursiveFilter(val: string) {
- return !val.includes(noRecursionHack);
- }
- export function HasFunctionFilter(val: string) {
- if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1;
- // add other function filters here...
- return undefined;
- }
-
- export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
- return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
- }
-
- export function HSLtoRGB(h: number, s: number, l: number) {
- // Must be fractions of 1
- // s /= 100;
- // l /= 100;
-
- const c = (1 - Math.abs(2 * l - 1)) * s,
- x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
- m = l - c / 2;
- let r = 0,
- g = 0,
- b = 0;
- if (0 <= h && h < 60) {
- r = c;
- g = x;
- b = 0;
- } else if (60 <= h && h < 120) {
- r = x;
- g = c;
- b = 0;
- } else if (120 <= h && h < 180) {
- r = 0;
- g = c;
- b = x;
- } else if (180 <= h && h < 240) {
- r = 0;
- g = x;
- b = c;
- } else if (240 <= h && h < 300) {
- r = x;
- g = 0;
- b = c;
- } else if (300 <= h && h < 360) {
- r = c;
- g = 0;
- b = x;
- }
- r = Math.round((r + m) * 255);
- g = Math.round((g + m) * 255);
- b = Math.round((b + m) * 255);
- return { r: r, g: g, b: b };
- }
-
- export function RGBToHSL(r: number, g: number, b: number) {
- // Make r, g, and b fractions of 1
- r /= 255;
- g /= 255;
- b /= 255;
-
- // Find greatest and smallest channel values
- const cmin = Math.min(r, g, b),
- cmax = Math.max(r, g, b),
- delta = cmax - cmin;
- let h = 0,
- s = 0,
- l = 0;
- // Calculate hue
-
- // No difference
- if (delta === 0) h = 0;
- // Red is max
- else if (cmax === r) h = ((g - b) / delta) % 6;
- // Green is max
- else if (cmax === g) h = (b - r) / delta + 2;
- // Blue is max
- else h = (r - g) / delta + 4;
-
- h = Math.round(h * 60);
-
- // Make negative hues positive behind 360°
- if (h < 0) h += 360; // Calculate lightness
-
- l = (cmax + cmin) / 2;
-
- // Calculate saturation
- s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
-
- // Multiply l and s by 100
- // s = +(s * 100).toFixed(1);
- // l = +(l * 100).toFixed(1);
-
- return { h: h, s: s, l: l };
- }
-
- export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) {
- if (!targetHgt) return targetY; // if there's no height, then assume that
- if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
- return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
- }
- if (scrollTop >= Math.max(0, targetY - minSpacing)) {
- return Math.max(0, Math.floor(targetY - minSpacing));
- }
- }
-
- export function clamp(n: number, lower: number, upper: number) {
- return Math.max(lower, Math.min(upper, n));
- }
-
- export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
- if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
- if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
- //if (xs2 > xe)
- return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
- }
- export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
- if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
- if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
- //if (ys2 > ye)
- return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
- }
-
export function rotPt(x: number, y: number, radAng: number) {
return { x: x * Math.cos(radAng) - y * Math.sin(radAng), y: x * Math.sin(radAng) + y * Math.cos(radAng) };
}
- function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
- if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
- const atob = { x: bx - ax, y: by - ay };
- const atop = { x: px - ax, y: py - ay };
- const len = atob.x * atob.x + atob.y * atob.y;
- var dot = atop.x * atob.x + atop.y * atob.y;
- const t = Math.min(1, Math.max(0, dot / len));
-
- dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
-
- return {
- point: {
- x: ax + atob.x * t,
- y: ay + atob.y * t,
- },
- left: dot < 1,
- dot: dot,
- t: t,
- };
- }
-
- export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) {
- const r = l + w,
- b = t + h;
- const r1 = l1 + w1,
- b1 = t1 + h1;
- const hsegs = [
- [l, r, t, l1, r1, t1],
- [l, r, b, l1, r1, t1],
- [l, r, t, l1, r1, b1],
- [l, r, b, l1, r1, b1],
- ];
- const vsegs = [
- [l, t, b, l1, t1, b1],
- [r, t, b, l1, t1, b1],
- [l, t, b, r1, t1, b1],
- [r, t, b, r1, t1, b1],
- ];
- const res = hsegs.reduce(
- (closest, seg) => {
- const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
- return res[0] < closest[0] ? res : closest;
- },
- [Number.MAX_VALUE, []] as [number, number[]]
- );
- const fres = vsegs.reduce((closest, seg) => {
- const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
- return res[0] < closest[0] ? res : closest;
- }, res);
+ export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, xIn: number, yIn: number) {
+ const r = l + w;
+ const b = t + h;
- const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
- return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
- }
+ const x = clamp(xIn, l, r);
+ const y = clamp(yIn, t, b);
- export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
- const r = l + w,
- b = t + h;
-
- (x = clamp(x, l, r)), (y = clamp(y, t, b));
-
- const dl = Math.abs(x - l),
- dr = Math.abs(x - r),
- dt = Math.abs(y - t),
- db = Math.abs(y - b);
+ const dl = Math.abs(x - l);
+ const dr = Math.abs(x - r);
+ const dt = Math.abs(y - t);
+ const db = Math.abs(y - b);
const m = Math.min(dl, dr, dt, db);
return m === dt ? [x, t] : m === db ? [x, b] : m === dl ? [l, y] : [r, y];
}
+}
+export function decimalToHexString(numberIn: number) {
+ const number = numberIn < 0 ? 0xffffffff + numberIn + 1 : numberIn;
+ return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
+}
- export function GetClipboardText(): string {
- const textArea = document.createElement('textarea');
- document.body.appendChild(textArea);
- textArea.focus();
- textArea.select();
-
- try {
- document.execCommand('paste');
- } catch (err) {}
-
- const val = textArea.value;
- document.body.removeChild(textArea);
- return val;
- }
-
- export const loggingEnabled: Boolean = false;
- export const logFilter: number | undefined = undefined;
-
- export function log(prefix: string, messageName: string, message: any, receiving: boolean) {
- if (!loggingEnabled) {
- return;
- }
- message = message || {};
- if (logFilter !== undefined && logFilter !== message.type) {
- return;
- }
- const idString = (message.id || '').padStart(36, ' ');
- prefix = prefix.padEnd(16, ' ');
- console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
- }
-
- export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
- return (args: any) => {
- log(prefix, messageName, args, true);
- func(args);
- };
- }
+export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
+ if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
+ if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
+ // if (xs2 > xe)
+ return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
+}
+export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
+ if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
+ if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
+ // if (ys2 > ye)
+ return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
+}
- export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
- log('Emit', message.Name, args, false);
- socket.emit(message.Message, args);
- }
+function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
+ if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
+ const atob = { x: bx - ax, y: by - ay };
+ const atop = { x: px - ax, y: py - ay };
+ const len = atob.x * atob.x + atob.y * atob.y;
+ let dot = atop.x * atob.x + atop.y * atob.y;
+ const t = Math.min(1, Math.max(0, dot / len));
- export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
- socket.on(message.Message, loggingCallback('Incoming', handler, message.Name));
- }
+ dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
- export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
- socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
- log('S receiving', message.Name, arg, true);
- handler([arg, loggingCallback('S sending', fn, message.Name)]);
- });
- }
- export type RoomHandler = (socket: Socket, room: string) => any;
- export type UsedSockets = Socket;
- export type RoomMessage = 'create or join' | 'created' | 'joined';
- export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
- socket.on(message, (room: any) => handler(socket, room));
- }
+ return {
+ point: {
+ x: ax + atob.x * t,
+ y: ay + atob.y * t,
+ },
+ left: dot < 1,
+ dot: dot,
+ t: t,
+ };
}
-export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
- const omit: any = { ...obj };
- const extract: any = {};
- keys.forEach(key => {
- extract[key] = omit[key];
- delete omit[key];
- });
- pattern &&
- Array.from(Object.keys(omit))
- .filter(key => key.match(pattern))
- .forEach(key => {
- extract[key] = omit[key];
- delete omit[key];
- });
- addKeyFunc?.(omit);
- return { omit, extract };
-}
+export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) {
+ const r = l + w;
+ const b = t + h;
+ const r1 = l1 + w1;
+ const b1 = t1 + h1;
+ const hsegs = [
+ [l, r, t, l1, r1, t1],
+ [l, r, b, l1, r1, t1],
+ [l, r, t, l1, r1, b1],
+ [l, r, b, l1, r1, b1],
+ ];
+ const vsegs = [
+ [l, t, b, l1, t1, b1],
+ [r, t, b, l1, t1, b1],
+ [l, t, b, r1, t1, b1],
+ [r, t, b, r1, t1, b1],
+ ];
+ const res = hsegs.reduce(
+ (closest, seg) => {
+ const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+ return res[0] < closest[0] ? res : closest;
+ },
+ [Number.MAX_VALUE, []] as [number, number[]]
+ );
+ const fres = vsegs.reduce((closest, seg) => {
+ const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+ return res[0] < closest[0] ? res : closest;
+ }, res);
-export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
- const dup: any = {};
- keys.forEach(key => (dup[key] = obj[key]));
- addKeyFunc && addKeyFunc(dup);
- return dup;
+ const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
+ return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
}
export function timenow() {
@@ -455,7 +154,6 @@ export function timenow() {
let ampm = 'am';
let h = now.getHours();
let m: any = now.getMinutes();
- const s: any = now.getSeconds();
if (h >= 12) {
if (h > 12) h -= 12;
ampm = 'pm';
@@ -464,14 +162,8 @@ export function timenow() {
return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
}
-export function incrementTitleCopy(title: string) {
- const numstr = title.match(/.*(\{([0-9]*)\})+/);
- const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`;
- return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr;
-}
-
-export function formatTime(time: number) {
- time = Math.round(time);
+export function formatTime(timeIn: number) {
+ const time = Math.round(timeIn);
const hours = Math.floor(time / 60 / 60);
const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
@@ -503,93 +195,31 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
}
export function stringHash(s?: string) {
+ // eslint-disable-next-line no-bitwise
return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0));
}
export function percent2frac(percent: string) {
return Number(percent.substr(0, percent.length - 1)) / 100;
}
+export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
+
+export type Predicate<K, V> = (entry: [K, V]) => boolean;
export function numberRange(num: number) {
return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : [];
}
-export function returnTransparent() {
- return 'transparent';
-}
-
-export function returnTrue() {
- return true;
-}
-
-export function returnIgnore(): 'ignore' {
- return 'ignore';
-}
-export function returnAlways(): 'always' {
- return 'always';
-}
-export function returnNever(): 'never' {
- return 'never';
-}
-
-export function returnDefault(): 'default' {
- return 'default';
-}
-
-export function return18() {
- return 18;
-}
-
-export function returnFalse() {
- return false;
-}
-
-export function returnAll(): 'all' {
- return 'all';
-}
-
-export function returnNone(): 'none' {
- return 'none';
-}
-
-export function returnVal(val1?: number, val2?: number) {
- return val1 ? val1 : val2 !== undefined ? val2 : 0;
-}
-
-export function returnOne() {
- return 1;
-}
-
-export function returnZero() {
- return 0;
-}
-
-export function returnEmptyString() {
- return '';
-}
-
-export function returnEmptyFilter() {
- return [] as string[];
-}
-
-export function returnEmptyDoclist() {
- return [] as any[];
-}
-
-export let emptyPath: DocumentView[] = [];
-
export function emptyFunction() {
return undefined;
}
+export const emptyPath: any[] = [];
+
export function unimplementedFunction() {
throw new Error('This function is not implemented, but should be.');
}
-export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
-
-export type Predicate<K, V> = (entry: [K, V]) => boolean;
-
export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
const deepCopy = new Map<K, V>();
const entries = source.entries();
@@ -616,197 +246,6 @@ export namespace JSONUtils {
}
}
-const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => {
- if (transition === 'linear') {
- let newCurrentTime = currentTime / duration; // currentTime / (duration / 2);
- return start + newCurrentTime * change;
- }
-
- let newCurrentTime = currentTime / (duration / 2);
- if (newCurrentTime < 1) {
- return (change / 2) * newCurrentTime * newCurrentTime + start;
- }
-
- newCurrentTime -= 1;
- return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
-};
-
-export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) {
- stopper?.();
- const elements = element instanceof HTMLElement ? [element] : element;
- const starts = elements.map(element => element.scrollTop);
- const startDate = new Date().getTime();
- let _stop = false;
- const stop = () => (_stop = true);
- const animateScroll = () => {
- const currentDate = new Date().getTime();
- const currentTime = currentDate - startDate;
- const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
- if (!_stop) {
- if (currentTime < duration) {
- elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
- requestAnimationFrame(animateScroll);
- } else {
- elements.forEach(element => setScrollTop(element, to));
- }
- }
- };
- animateScroll();
- return stop;
-}
-
-export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
- const elements = element instanceof HTMLElement ? [element] : element;
- const starts = elements.map(element => element.scrollLeft);
- const startDate = new Date().getTime();
-
- const animateScroll = () => {
- const currentDate = new Date().getTime();
- const currentTime = currentDate - startDate;
- elements.map((element, i) => (element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration)));
-
- if (currentTime < duration) {
- requestAnimationFrame(animateScroll);
- } else {
- elements.forEach(element => (element.scrollLeft = to));
- }
- };
- animateScroll();
-}
-
-export function addStyleSheet(styleType: string = 'text/css') {
- const style = document.createElement('style');
- style.type = styleType;
- const sheets = document.head.appendChild(style);
- return (sheets as any).sheet;
-}
-export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
- const propText =
- typeof css === 'string'
- ? css
- : Object.keys(css)
- .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]))
- .join(';');
- return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
-}
-export function removeStyleSheetRule(sheet: any, rule: number) {
- if (sheet.rules.length) {
- sheet.removeRule(rule);
- return true;
- }
- return false;
-}
-export function clearStyleSheetRules(sheet: any) {
- if (sheet.rules.length) {
- numberRange(sheet.rules.length).map(n => sheet.removeRule(0));
- return true;
- }
- return false;
-}
-
-export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) {
- if (!element) return;
- ['pointerdown', 'pointerup'].map(event => {
- const me = new PointerEvent(event, {
- view: window,
- bubbles: true,
- cancelable: true,
- button: 2,
- pointerType: 'mouse',
- clientX: x,
- clientY: y,
- screenX: sx,
- screenY: sy,
- });
- (me as any).dash = true;
- element.dispatchEvent(me);
- });
-
- if (rightClick) {
- const me = new MouseEvent('contextmenu', {
- view: window,
- bubbles: true,
- cancelable: true,
- button: 2,
- clientX: x,
- clientY: y,
- movementX: 0,
- movementY: 0,
- screenX: sx,
- screenY: sy,
- });
- (me as any).dash = true;
- element.dispatchEvent(me);
- }
-}
-
-export function DashColor(color: string) {
- try {
- return color ? Color(color.toLowerCase()) : Color('transparent');
- } catch (e) {
- if (color.includes('gradient')) console.log("using color 'white' in place of :" + color);
- else console.log('COLOR error:', e);
- return Color('white');
- }
-}
-
-export function lightOrDark(color: any) {
- if (color === 'transparent' || !color) return Colors.BLACK;
- if (color.startsWith?.('linear')) return Colors.BLACK;
- if (DashColor(color).isLight()) return Colors.BLACK;
- return Colors.WHITE;
-}
-
-export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
- if (elem.tagName === 'INPUT') return 'input';
- if (elem.tagName === 'TEXTAREA') return 'textarea';
- if (elem.nodeType === elem.TEXT_NODE) {
- const range = elem.ownerDocument.createRange();
- range.selectNodeContents(elem);
- var currentPos = 0;
- const endPos = range.endOffset;
- while (currentPos + 1 <= endPos) {
- range.setStart(elem, currentPos);
- range.setEnd(elem, currentPos + 1);
- const rangeRect = range.getBoundingClientRect();
- if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
- range.expand?.('word'); // doesn't exist in firefox
- const ret = range.toString();
- range.detach();
- return ret;
- }
- currentPos += 1;
- }
- } else {
- for (const childNode of elem.childNodes) {
- const range = childNode.ownerDocument.createRange();
- range.selectNodeContents(childNode);
- const rangeRect = range.getBoundingClientRect();
- if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
- range.detach();
- const word = getWordAtPoint(childNode, x, y);
- if (word) return word;
- } else {
- range.detach();
- }
- }
- }
- return undefined;
-}
-
-export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) {
- let entered = false;
- for (let child = target; !entered && child; child = child.parentElement) {
- entered = child === ele;
- }
- return entered;
-}
-
-export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
- e.stopPropagation();
- e.preventDefault();
-}
-
/**
* Helper method for converting pixel string eg. '32px' into number eg. 32
* @param value: string with 'px' ending
@@ -816,100 +255,10 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
* '32px' -> 32
*/
export function numberValue(value: string | undefined): number {
- if (value == undefined) return 0;
+ if (value === undefined) return 0;
return parseInt(value);
}
export function numbersAlmostEqual(num1: number, num2: number) {
return Math.abs(num1 - num2) < 0.2;
}
-
-export function setupMoveUpEvents(
- target: object,
- e: React.PointerEvent,
- moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
- upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any,
- clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
- stopPropagation: boolean = true,
- stopMovePropagation: boolean = true,
- noDoubleTapTimeout?: () => void
-) {
- const doubleTapTimeout = 300;
- (target as any)._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout;
- (target as any)._lastTap = Date.now();
- (target as any)._downX = (target as any)._lastX = e.clientX;
- (target as any)._downY = (target as any)._lastY = e.clientY;
- (target as any)._noClick = false;
- var moving = false;
-
- const _moveEvent = (e: PointerEvent): void => {
- if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
- moving = true;
- if ((target as any)._doubleTime) {
- clearTimeout((target as any)._doubleTime);
- (target as any)._doubleTime = undefined;
- }
- if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
- document.removeEventListener('pointermove', _moveEvent);
- document.removeEventListener('pointerup', _upEvent);
- }
- }
- (target as any)._lastX = e.clientX;
- (target as any)._lastY = e.clientY;
- stopMovePropagation && e.stopPropagation();
- };
- const _upEvent = (e: PointerEvent): void => {
- const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4;
- upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick);
- if (isClick) {
- if (!(target as any)._doubleTime && noDoubleTapTimeout) {
- (target as any)._doubleTime = setTimeout(() => {
- noDoubleTapTimeout?.();
- (target as any)._doubleTime = undefined;
- }, doubleTapTimeout);
- }
- if ((target as any)._doubleTime && (target as any)._doubleTap) {
- clearTimeout((target as any)._doubleTime);
- (target as any)._doubleTime = undefined;
- }
- (target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
- }
- document.removeEventListener('pointermove', _moveEvent);
- document.removeEventListener('pointerup', _upEvent, true);
- };
- const _clickEvent = (e: MouseEvent): void => {
- if ((target as any)._noClick) e.stopPropagation();
- document.removeEventListener('click', _clickEvent, true);
- };
- if (stopPropagation) {
- e.stopPropagation();
- e.preventDefault();
- }
- document.addEventListener('pointermove', _moveEvent);
- document.addEventListener('pointerup', _upEvent, true);
- document.addEventListener('click', _clickEvent, true);
-}
-
-export function DivHeight(ele: HTMLElement): number {
- return Number(getComputedStyle(ele).height.replace('px', ''));
-}
-export function DivWidth(ele: HTMLElement): number {
- return Number(getComputedStyle(ele).width.replace('px', ''));
-}
-
-export function dateRangeStrToDates(dateStr: string) {
- // dateStr in yyyy-mm-dd format
- const dateRangeParts = dateStr.split('|'); // splits into from and to date
- const fromParts = dateRangeParts[0].split('-');
- const toParts = dateRangeParts[1].split('-');
-
- const fromYear = parseInt(fromParts[0]);
- const fromMonth = parseInt(fromParts[1]) - 1;
- const fromDay = parseInt(fromParts[2]);
-
- const toYear = parseInt(toParts[0]);
- const toMonth = parseInt(toParts[1]) - 1;
- const toDay = parseInt(toParts[2]);
-
- return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
-}
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 321572071..cf7a61d24 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,20 +1,15 @@
import { runInAction } from 'mobx';
-import * as rp from 'request-promise';
-import { Doc, DocListCast, Opt } from '../fields/Doc';
+import { Socket, io } from 'socket.io-client';
+import { ClientUtils } from '../ClientUtils';
+import { Utils, emptyFunction } from '../Utils';
+import { Doc, 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 { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
-import { DocCast, StrCast } from '../fields/Types';
-//import MobileInkOverlay from '../mobile/MobileInkOverlay';
-import { io, Socket } from 'socket.io-client';
-import { emptyFunction, Utils } from '../Utils';
-import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
-import { DocumentType } from './documents/DocumentTypes';
-import { LinkManager } from './util/LinkManager';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message';
import { SerializationHelper } from './util/SerializationHelper';
-//import { GestureOverlay } from './views/GestureOverlay';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -30,23 +25,32 @@ import { SerializationHelper } from './util/SerializationHelper';
* or update ourselves based on the server's update message, that occurs here
*/
export namespace DocServer {
- let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ // eslint-disable-next-line import/no-mutable-exports
+ export let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+
+ function errorFunc(): never {
+ throw new Error("Can't use DocServer without calling init first");
+ }
+ let _UpdateField: (id: string, diff: any) => void = errorFunc;
+ let _CreateField: (field: RefField) => void = errorFunc;
export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
}
export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
- //log('Emit', message.Name, args, false);
+ // log('Emit', message.Name, args, false);
socket.emit(message.Message, args);
}
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
- //log('Emit', message.Name, args, false);
+ // log('Emit', message.Name, args, false);
if (fn) {
socket.emit(message.Message, args, Utils.loggingCallback('Receiving', fn, message.Name));
} else {
- return new Promise<any>(res => socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name)));
+ return new Promise<any>(res => {
+ socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name));
+ });
}
}
@@ -59,57 +63,21 @@ export namespace DocServer {
return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
}
- 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);
- Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
- }
- });
- LinkManager.Instance.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
- Doc.MyDockedBtns.linearView_IsOpen && 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)'));
- Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
-
- rp.post(Utils.prepend('/setCacheDocumentIds'), {
- body: {
- cacheDocumentIds,
- },
- json: true,
- });
- }
- export let _socket: Socket;
+ let _socket: Socket;
// this client's distinct GUID created at initialization
let USER_ID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
export enum WriteMode {
- Default = 0, //Anything goes
- Playground = 1, //Playground (write own/no read other updates)
- LiveReadonly = 2, //Live Readonly (no write/read others)
- LivePlayground = 3, //Live Playground (write own/read others)
+ Default = 0, // Anything goes
+ Playground = 1, // Playground (write own/no read other updates)
+ LiveReadonly = 2, // Live Readonly (no write/read others)
+ LivePlayground = 3, // Live Playground (write own/read others)
}
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
- export var PlaygroundFields: string[] = [];
+ export const PlaygroundFields: string[] = [];
export function setLivePlaygroundFields(livePlaygroundFields: string[]) {
DocServer.PlaygroundFields.push(...livePlaygroundFields);
livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
@@ -134,7 +102,7 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return Doc.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
+ return ClientUtils.CurrentUserEmail() === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -185,56 +153,14 @@ export namespace DocServer {
window.location.reload();
}
- export function init(protocol: string, hostname: string, port: number, identifier: string) {
- _cache = {};
- USER_ID = identifier;
- protocol = protocol.startsWith('https') ? 'wss' : 'ws';
- _socket = io(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
- _socket.on("connect_error", (err:any) => console.log(err));
- // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
-
- _GetCachedRefField = _GetCachedRefFieldImpl;
- _GetRefField = _GetRefFieldImpl;
- _GetRefFields = _GetRefFieldsImpl;
- _CreateField = _CreateFieldImpl;
- _UpdateField = _UpdateFieldImpl;
-
- /**
- * Whenever the server sends us its handshake message on our
- * websocket, we use the above function to return the handshake.
- */
- DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
- DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
- DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
- DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
-
- // // mobile ink overlay socket events to communicate between mobile view and desktop view
- // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
- // // MobileInkOverlay.Instance.drawStroke(content);
- // });
- // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
- // //GestureOverlay.Instance.enableMobileInkOverlay(content);
- // // MobileInkOverlay.Instance.initMobileInkOverlay(content);
- // });
- // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
- // // MobileInkOverlay.Instance.updatePosition(content);
- // });
- // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
- // // MobileInkOverlay.Instance.uploadDocument(content);
- // });
- }
-
- function errorFunc(): never {
- throw new Error("Can't use DocServer without calling init first");
- }
-
export namespace Control {
let _isReadOnly = false;
export function makeReadOnly() {
if (!_isReadOnly) {
_isReadOnly = true;
- _CreateField = field => (_cache[field[Id]] = field);
+ _CreateField = field => {
+ _cache[field[Id]] = field;
+ };
_UpdateField = emptyFunction;
// _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
}
@@ -313,7 +239,8 @@ export namespace DocServer {
});
cached[UpdatingFromServer] = false;
return cached;
- } else if (field !== undefined) {
+ }
+ if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -327,24 +254,25 @@ export namespace DocServer {
// being retrieved and cached
!force && (_cache[id] = deserializeField);
return force ? (cached as any) : deserializeField;
- } else if (cached instanceof Promise) {
+ }
+ if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
// return this promise which will resolve to the field itself (see 7)
return cached;
- } else {
- // CACHED => great, let's just return the cached field we have
- return Promise.resolve(cached).then(field => {
- //(field instanceof Doc) && fetchProto(field);
- return field;
- });
}
+ // CACHED => great, let's just return the cached field we have
+ return Promise.resolve(cached).then(
+ field => field
+ // (field instanceof Doc) && fetchProto(field);
+ );
};
const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
const cached = _cache[id];
if (cached !== undefined && !(cached instanceof Promise)) {
return cached;
}
+ return undefined;
};
let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
@@ -358,7 +286,7 @@ export namespace DocServer {
}
export async function getYoutubeChannels() {
- return await DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
+ return DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
}
export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
@@ -379,21 +307,24 @@ export namespace DocServer {
const requestedIds: string[] = [];
const promises: Promise<any>[] = [];
- let defaultRes: any = undefined;
- const defaultPromise = new Promise<any>(res => (defaultRes = res));
- let defaultPromises: { p: Promise<any>; id: string }[] = [];
+ let defaultRes: any;
+ const defaultPromise = new Promise<any>(res => {
+ defaultRes = res;
+ });
+ const defaultPromises: { p: Promise<any>; id: string }[] = [];
// 1) an initial pass through the cache to determine
// 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.filter(id => id)) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const id of ids.filter(filterid => filterid)) {
const cached = _cache[id];
if (cached === undefined) {
defaultPromises.push({
id,
- p: (_cache[id] = new Promise<any>(async res => {
- await defaultPromise;
- res(_cache[id]);
+ // eslint-disable-next-line no-loop-func
+ p: (_cache[id] = new Promise<any>(res => {
+ defaultPromise.then(() => res(_cache[id]));
})),
});
// NOT CACHED => we'll have to send a request to the server
@@ -415,7 +346,11 @@ 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);
- setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
+ setTimeout(() =>
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.requested = requestedIds.length;
+ })
+ );
const serializedFields = await DocServer.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
@@ -423,19 +358,27 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
let processed = 0;
console.log('deserializing ' + serializedFields.length + ' fields');
+ // eslint-disable-next-line no-restricted-syntax
for (const field of serializedFields) {
processed++;
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
+ // eslint-disable-next-line no-loop-func
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.retrieved = processed;
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await new Promise(res => {
+ setTimeout(res);
+ }); // force loading to yield to splash screen rendering to update progress
}
const cached = _cache[field.id];
if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) {
// deserialize
// adds to a list of promises that will be awaited asynchronously
promises.push(
+ // eslint-disable-next-line no-loop-func
(_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
- //overwrite or delete any promises (that we inserted as flags
+ // overwrite or delete any promises (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
if (deserialized !== undefined) {
@@ -455,7 +398,7 @@ export namespace DocServer {
// get the resolved field
} else if (cached instanceof Promise) {
console.log('.');
- //promises.push(cached);
+ // promises.push(cached);
} else if (field) {
// console.log('-');
}
@@ -472,7 +415,7 @@ export namespace DocServer {
// 6) with this confidence, we can now go through and update the cache at the ids of the fields that
// we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
// id to the soon-to-be-returned field mapping.
- //ids.forEach(id => (map[id] = _cache[id] as any));
+ // ids.forEach(id => (map[id] = _cache[id] as any));
// 7) those promises we encountered in the else if of 1), which represent
// other callers having already submitted a request to the server for (a) document(s)
@@ -481,7 +424,7 @@ export namespace DocServer {
//
// fortunately, those other callers will also hit their own version of 6) and clean up
// the shared cache when these promises resolve, so all we have to do is...
- //const otherCallersFetching = await Promise.all(promises);
+ // const otherCallersFetching = await Promise.all(promises);
// ...extract the RefFields returned from the resolution of those promises and add them to our
// own map.
// waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
@@ -506,6 +449,10 @@ export namespace DocServer {
}
// WRITE A NEW DOCUMENT TO THE SERVER
+ let _cacheNeedsUpdate = false;
+ export function CacheNeedsUpdate() {
+ return _cacheNeedsUpdate;
+ }
/**
* A wrapper around the function local variable _createField.
@@ -514,18 +461,16 @@ 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;
+ _cacheNeedsUpdate = true;
_CreateField(field);
}
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
}
- let _CreateField: (field: RefField) => void = errorFunc;
-
// NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE
/**
@@ -541,13 +486,11 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
- let _UpdateField: (id: string, diff: any) => void = errorFunc;
-
function _respondToUpdateImpl(diff: any) {
- const id = diff.id;
+ const { id } = diff;
// to be valid, the Diff object must reference
// a document's id
if (id === undefined) {
@@ -578,11 +521,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
@@ -596,7 +539,7 @@ export namespace DocServer {
}
}
- let _RespondToUpdate = _respondToUpdateImpl;
+ const _RespondToUpdate = _respondToUpdateImpl;
const _respondToDelete = _respondToDeleteImpl;
function respondToUpdate(diff: any) {
@@ -606,4 +549,28 @@ export namespace DocServer {
function respondToDelete(ids: string | string[]) {
_respondToDelete(ids);
}
+
+ export function init(protocol: string, hostname: string, port: number, identifier: string) {
+ _cache = {};
+ USER_ID = identifier;
+ _socket = io(`${protocol.startsWith('https') ? 'wss' : 'ws'}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+ _socket.on('connect_error', (err: any) => console.log(err));
+ // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+
+ _GetCachedRefField = _GetCachedRefFieldImpl;
+ SetObjGetRefField((_GetRefField = _GetRefFieldImpl));
+ SetObjGetRefFields((_GetRefFields = _GetRefFieldsImpl));
+ _CreateField = _CreateFieldImpl;
+ _UpdateField = _UpdateFieldImpl;
+
+ /**
+ * Whenever the server sends us its handshake message on our
+ * websocket, we use the above function to return the handshake.
+ */
+ DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
+ DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+ }
}
diff --git a/src/client/Network.ts b/src/client/Network.ts
index cdcd5225a..968c407b2 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -1,5 +1,6 @@
-import { Utils } from '../Utils';
import * as requestPromise from 'request-promise';
+import { ClientUtils } from '../ClientUtils';
+import { Utils } from '../Utils';
import { Upload } from '../server/SharedMediaTypes';
/**
@@ -14,7 +15,7 @@ export namespace Networking {
export async function PostToServer(relativeRoute: string, body?: any) {
const options = {
- uri: Utils.prepend(relativeRoute),
+ uri: ClientUtils.prepend(relativeRoute),
method: 'POST',
body,
json: true,
@@ -49,14 +50,14 @@ export namespace Networking {
}
const maxFileSize = 50000000;
if (fileguidpairs.some(f => f.file.size > maxFileSize)) {
- return new Promise<any>(res =>
+ return new Promise<any>(res => {
res([
{
source: { name: '', type: '', size: 0, toJson: () => ({ name: '', type: '' }) },
result: { name: '', message: `max file size (${maxFileSize / 1000000}MB) exceeded` },
},
- ])
- );
+ ]);
+ });
}
formData.set('fileguids', fileguidpairs.map(pair => pair.guid).join(';'));
formData.set('filesize', fileguidpairs.reduce((sum, pair) => sum + pair.file.size, 0).toString());
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 855f48f7e..5269f763b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,14 +1,14 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Opt } from "../../fields/Doc";
-import { Networking } from "../Network";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { MainViewModal } from "../views/MainViewModal";
-import "./GoogleAuthenticationManager.scss";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Opt } from '../../fields/Doc';
+import { Networking } from '../Network';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { MainViewModal } from '../views/MainViewModal';
+import './GoogleAuthenticationManager.scss';
-const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
-const prompt = "Paste authorization code here...";
+const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
+const prompt = 'Paste authorization code here...';
@observer
export class GoogleAuthenticationManager extends React.Component<{}> {
@@ -23,11 +23,11 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
private disposer: Opt<IReactionDisposer>;
private set isOpen(value: boolean) {
- runInAction(() => this.openState = value);
+ runInAction(() => (this.openState = value));
}
private set shouldShowPasteTarget(value: boolean) {
- runInAction(() => this.showPasteTargetState = value);
+ runInAction(() => (this.showPasteTargetState = value));
}
public cancel() {
@@ -35,7 +35,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
}
public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
- let response: any = await Networking.FetchFromServer("/readGoogleAccessToken");
+ let response: any = await Networking.FetchFromServer('/readGoogleAccessToken');
// if this is an authentication url, activate the UI to register the new access token
if (new RegExp(AuthenticationUrl).test(response)) {
this.isOpen = true;
@@ -47,7 +47,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
async authenticationCode => {
if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
this.disposer?.();
- const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+ const response = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
runInAction(() => {
this.success = true;
this.credentials = response;
@@ -71,7 +71,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
this.isOpen = true;
}
return response.access_token;
- }
+ };
resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
if (!visibleForMS && !fadesOutInMS) {
@@ -89,14 +89,20 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
this.displayLauncher = false;
this.shouldShowPasteTarget = false;
if (visibleForMS > 0 && fadesOutInMS > 0) {
- setTimeout(action(() => {
- this.isOpen = false;
- setTimeout(action(() => {
- this.success = undefined;
- this.displayLauncher = true;
- this.credentials = undefined;
- }), fadesOutInMS);
- }), visibleForMS);
+ setTimeout(
+ action(() => {
+ this.isOpen = false;
+ setTimeout(
+ action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ }),
+ fadesOutInMS
+ );
+ }),
+ visibleForMS
+ );
}
});
@@ -108,61 +114,44 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
private get renderPrompt() {
return (
<div className={'authorize-container'}>
-
- {this.displayLauncher ? <button
- className={"dispatch"}
- onClick={() => {
- window.open(this.authenticationLink);
- setTimeout(() => this.shouldShowPasteTarget = true, 500);
- }}
- style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
- >Authorize a Google account...</button> : (null)}
- {this.showPasteTargetState ? <input
- className={'paste-target'}
- onChange={action(e => this.authenticationCode = e.currentTarget.value)}
- placeholder={prompt}
- /> : (null)}
- {this.credentials ?
+ {this.displayLauncher ? (
+ <button
+ className={'dispatch'}
+ onClick={() => {
+ window.open(this.authenticationLink);
+ setTimeout(() => (this.shouldShowPasteTarget = true), 500);
+ }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}>
+ Authorize a Google account...
+ </button>
+ ) : null}
+ {this.showPasteTargetState ? <input className={'paste-target'} onChange={action(e => (this.authenticationCode = e.currentTarget.value))} placeholder={prompt} /> : null}
+ {this.credentials ? (
<>
- <img
- className={'avatar'}
- src={this.credentials.userInfo.picture}
- />
- <span
- className={'welcome'}
- >Welcome to Dash, {this.credentials.userInfo.name}
- </span>
+ <img className={'avatar'} src={this.credentials.userInfo.picture} />
+ <span className={'welcome'}>Welcome to Dash, {this.credentials.userInfo.name}</span>
<div
className={'disconnect'}
onClick={async () => {
- await Networking.FetchFromServer("/revokeGoogleAccessToken");
+ await Networking.FetchFromServer('/revokeGoogleAccessToken');
this.resetState(0, 0);
- }}
- >Disconnect Account</div>
- </> : (null)}
+ }}>
+ Disconnect Account
+ </div>
+ </>
+ ) : null}
</div>
);
}
private get dialogueBoxStyle() {
- const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
- return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
+ const borderColor = this.success === undefined ? 'black' : this.success ? 'green' : 'red';
+ return { borderColor, transition: '0.2s borderColor ease', zIndex: 1002 };
}
render() {
- return (
- <MainViewModal
- isDisplayed={this.openState}
- interactive={true}
- contents={this.renderPrompt}
- // overlayDisplayedOpacity={0.9}
- dialogueBoxStyle={this.dialogueBoxStyle}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={action(() => this.isOpen = false)}
- />
- );
+ return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />;
}
-
}
-ScriptingGlobals.add("GoogleAuthenticationManager", GoogleAuthenticationManager); \ No newline at end of file
+ScriptingGlobals.add('GoogleAuthenticationManager', GoogleAuthenticationManager);
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index e8fd8fb8a..1eb977813 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,18 +1,18 @@
+/* eslint-disable no-use-before-define */
+import Photos = require('googlephotos');
import { AssertionError } from 'assert';
import { EditorState } from 'prosemirror-state';
+import { ClientUtils } from '../../../ClientUtils';
import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { RichTextUtils } from '../../../fields/RichTextUtils';
-import { Cast, StrCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
+import { Cast, ImageCast, StrCast } from '../../../fields/Types';
import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes';
-import { Utils } from '../../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
+import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox';
import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager';
-import Photos = require('googlephotos');
export namespace GooglePhotos {
const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken());
@@ -76,17 +76,16 @@ export namespace GooglePhotos {
export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => {
const { collection, title, descriptionKey, tag } = options;
const dataDocument = Doc.GetProto(collection);
- const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField));
+ const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => ImageCast(doc.data));
if (!images || !images.length) {
return undefined;
}
- const resolved = title ? title : StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
+ const resolved = title || StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
const { id, productUrl } = await Create.Album(resolved);
const response = await Transactions.UploadImages(images, { id }, descriptionKey);
if (response) {
const { results, failed } = response;
- let index: Opt<number>;
- while ((index = failed.pop()) !== undefined) {
+ for (let index = failed.pop(); index !== undefined; index = failed.pop()) {
Doc.RemoveDocFromList(dataDocument, 'data', images.splice(index, 1)[0]);
}
const mediaItems: MediaItem[] = results.map(item => item.mediaItem);
@@ -97,13 +96,12 @@ export namespace GooglePhotos {
for (let i = 0; i < images.length; i++) {
const image = Doc.GetProto(images[i]);
const mediaItem = mediaItems[i];
- if (!mediaItem) {
- continue;
+ if (mediaItem) {
+ image.googlePhotosId = mediaItem.id;
+ image.googlePhotosAlbumUrl = productUrl;
+ image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
+ idMapping[mediaItem.id] = image;
}
- image.googlePhotosId = mediaItem.id;
- image.googlePhotosAlbumUrl = productUrl;
- image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
- idMapping[mediaItem.id] = image;
}
collection.googlePhotosAlbumUrl = productUrl;
collection.googlePhotosIdMapping = idMapping;
@@ -111,9 +109,10 @@ export namespace GooglePhotos {
await Query.TagChildImages(collection);
}
collection.albumId = id;
- Transactions.AddTextEnrichment(collection, `Find me at ${Utils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
+ Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
return { albumId: id, mediaItems };
}
+ return undefined;
};
}
@@ -124,7 +123,7 @@ export namespace GooglePhotos {
await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
const response = await Query.ContentSearch(requested);
const uploads = await Transactions.WriteMediaItemsToServer(response);
- const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/));
+ const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /* , {"data_contentSize":upload.contentSize} */));
const options = { _width: 500, _height: 500 };
return constructor(children, options);
};
@@ -144,7 +143,7 @@ export namespace GooglePhotos {
const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto);
images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
- for (const value of values) {
+ values.forEach(async value => {
const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
searched?.forEach(async id => {
const image = await Cast(idMapping[id], Doc);
@@ -154,7 +153,7 @@ export namespace GooglePhotos {
!tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
}
});
- }
+ });
images?.forEach(image => {
const concatenated = tagMapping.get(image[Id])!;
const tags = concatenated.split(delimiter);
@@ -200,9 +199,10 @@ export namespace GooglePhotos {
export const AlbumSearch = async (albumId: string, pageSize = 100): Promise<MediaItem[]> => {
const photos = await endpoint();
const mediaItems: MediaItem[] = [];
- let nextPageTokenStored: Opt<string> = undefined;
+ let nextPageTokenStored: Opt<string>;
const found = 0;
do {
+ // eslint-disable-next-line no-await-in-loop
const response: any = await photos.mediaItems.search(albumId, pageSize, nextPageTokenStored);
mediaItems.push(...response.mediaItems);
nextPageTokenStored = response.nextPageToken;
@@ -222,7 +222,7 @@ export namespace GooglePhotos {
excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category));
filters.setContentFilter(contentFilter);
- const date = options.date;
+ const { date } = options;
if (date) {
const dateFilter = new photos.DateFilter();
if (date instanceof Date) {
@@ -240,15 +240,11 @@ export namespace GooglePhotos {
});
};
- export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => {
- return (await endpoint()).mediaItems.get(mediaItemId);
- };
+ export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => (await endpoint()).mediaItems.get(mediaItemId);
}
namespace Create {
- export const Album = async (title: string) => {
- return (await endpoint()).albums.create(title);
- };
+ export const Album = async (title: string) => (await endpoint()).albums.create(title);
}
export namespace Transactions {
@@ -278,6 +274,7 @@ export namespace GooglePhotos {
return enrichmentItem.id;
}
}
+ return undefined;
};
export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise<UploadInformation[]> => {
@@ -291,9 +288,12 @@ export namespace GooglePhotos {
return undefined;
}
const baseUrls: string[] = await Promise.all(
- response.results.map(item => {
- return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
- })
+ response.results.map(
+ item =>
+ new Promise<string>(resolve => {
+ Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl));
+ })
+ )
);
return baseUrls;
};
@@ -303,31 +303,29 @@ export namespace GooglePhotos {
failed: number[];
}
- export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
+ export const UploadImages = async (sources: Doc[], albumIn?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- if (album && 'title' in album) {
- album = await Create.Album(album.title);
- }
+ const album = albumIn && 'title' in albumIn ? await Create.Album(albumIn.title) : albumIn;
const media: MediaInput[] = [];
- for (const source of sources) {
- const data = Cast(Doc.GetProto(source).data, ImageField);
- if (!data) {
- return;
- }
- const url = data.url.href;
- const target = Doc.MakeEmbedding(source);
- const description = parseDescription(target, descriptionKey);
- await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
- media.push({ url, description });
- }
+ sources
+ .filter(source => ImageCast(Doc.GetProto(source).data))
+ .forEach(async source => {
+ const data = ImageCast(Doc.GetProto(source).data);
+ const url = data.url.href;
+ const target = Doc.MakeEmbedding(source);
+ const description = parseDescription(target, descriptionKey);
+ await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+ media.push({ url, description });
+ });
if (media.length) {
const results = await Networking.PostToServer('/googlePhotosMediaPost', { media, album });
return results;
}
+ return undefined;
};
const parseDescription = (document: Doc, descriptionKey: string) => {
- let description: string = Utils.prepend(`/doc/${document[Id]}?sharing=true`);
+ let description: string = ClientUtils.prepend(`/doc/${document[Id]}?sharing=true`);
const target = document[descriptionKey];
if (typeof target === 'string') {
description = target;
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 30194f9f8..fc4347a64 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -27,6 +27,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
};
+let lastCall = '';
+let lastResp = '';
/**
* Calls the OpenAI API.
*
@@ -36,11 +38,13 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => {
if (callType === GPTCallType.SUMMARY) inputText += '.';
const opts: GPTCallOpts = callTypeMap[callType];
+ if (lastCall === inputText) return lastResp;
try {
const configuration: ClientOptions = {
apiKey: process.env.OPENAI_KEY,
dangerouslyAllowBrowser: true,
};
+ lastCall = inputText;
const openai = new OpenAI(configuration);
let usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
@@ -55,8 +59,8 @@ const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any
temperature: opts.temp,
messages,
});
- const content = response.choices[0].message.content;
- return content;
+ lastResp = response.choices[0].message.content ?? '';
+ return lastResp;
} catch (err) {
console.log(err);
return 'Error connecting with API.';
@@ -79,8 +83,8 @@ const gptImageCall = async (prompt: string, n?: number) => {
return response.data.map(data => data.url);
} catch (err) {
console.error(err);
- return;
}
+ return undefined;
};
export { gptAPICall, gptImageCall, GPTCallType };
diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss
deleted file mode 100644
index eabdbb1ac..000000000
--- a/src/client/apis/youtube/YoutubeBox.scss
+++ /dev/null
@@ -1,126 +0,0 @@
-.youtubeBox-cont {
- ul {
- list-style-type: none;
- padding-inline-start: 10px;
- }
-
-
- li {
- margin: 4px;
- display: inline-flex;
- }
-
- li:hover {
- cursor: pointer;
- opacity: 0.8;
- }
-
- .search_wrapper {
- width: 100%;
- display: inline-flex;
- height: 175px;
-
- .video_duration {
- // margin: 0;
- // padding: 0;
- border: 0;
- background: transparent;
- display: inline-block;
- position: relative;
- bottom: 25px;
- left: 85%;
- margin: 4px;
- color: #FFFFFF;
- background-color: rgba(0, 0, 0, 0.80);
- padding: 2px 4px;
- border-radius: 2px;
- letter-spacing: .5px;
- font-size: 1.2rem;
- font-weight: 500;
- line-height: 1.2rem;
-
- }
-
- .textual_info {
- font-family: Arial, Helvetica, sans-serif;
-
- .videoTitle {
- margin-left: 4px;
- // display: inline-block;
- color: #0D0D0D;
- -webkit-line-clamp: 2;
- display: block;
- max-height: 4.8rem;
- overflow: hidden;
- font-size: 1.8rem;
- font-weight: 400;
- line-height: 2.4rem;
- -webkit-box-orient: vertical;
- text-overflow: ellipsis;
- white-space: normal;
- display: -webkit-box;
- }
-
- .channelName {
- color: #606060;
- margin-left: 4px;
- font-size: 1.3rem;
- font-weight: 400;
- line-height: 1.8rem;
- text-transform: none;
- margin-top: 0px;
- display: inline-block;
- }
-
- .video_description {
- margin-left: 4px;
- // font-size: 12px;
- color: #606060;
- padding-top: 8px;
- margin-bottom: 8px;
- display: block;
- line-height: 1.8rem;
- max-height: 4.2rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
- .publish_time {
- //display: inline-block;
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
- .viewCount {
-
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
-
-
- }
- }
-} \ No newline at end of file
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
deleted file mode 100644
index d3a15cd84..000000000
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ /dev/null
@@ -1,369 +0,0 @@
-import { action, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import { Doc, DocListCastAsync } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { Utils } from '../../../Utils';
-import { DocServer } from '../../DocServer';
-import { Docs } from '../../documents/Documents';
-import { DocumentView } from '../../views/nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-import '../../views/nodes/WebBox.scss';
-import './YoutubeBox.scss';
-import * as React from 'react';
-import { SnappingManager } from '../../util/SnappingManager';
-
-interface VideoTemplate {
- thumbnailUrl: string;
- videoTitle: string;
- videoId: string;
- duration: string;
- channelTitle: string;
- viewCount: string;
- publishDate: string;
- videoDescription: string;
-}
-
-/**
- * This class models the youtube search document that can be dropped on to canvas.
- */
-@observer
-export class YoutubeBox extends React.Component<FieldViewProps> {
- @observable YoutubeSearchElement: HTMLInputElement | undefined;
- @observable searchResultsFound: boolean = false;
- @observable searchResults: any[] = [];
- @observable videoClicked: boolean = false;
- @observable selectedVideoUrl: string = '';
- @observable lisOfBackUp: JSX.Element[] = [];
- @observable videoIds: string | undefined;
- @observable videoDetails: any[] = [];
- @observable curVideoTemplates: VideoTemplate[] = [];
-
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(YoutubeBox, fieldKey);
- }
-
- /**
- * When component mounts, last search's results are laoded in based on the back up stored
- * in the document of the props.
- */
- async componentDidMount() {
- //DocServer.getYoutubeChannels();
- const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
- const awaitedBackUp = await castedSearchBackUp;
- const castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc);
- const awaitedDetails = await castedDetailBackUp;
-
- if (awaitedBackUp) {
- const jsonList = await DocListCastAsync(awaitedBackUp.json);
- const jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
-
- if (jsonList!.length !== 0) {
- runInAction(() => (this.searchResultsFound = true));
- let index = 0;
- //getting the necessary information from backUps and building templates that will be used to map in render
- for (const video of jsonList!) {
- const videoId = await Cast(video.id, Doc);
- const id = StrCast(videoId!.videoId);
- const snippet = await Cast(video.snippet, Doc);
- const videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title));
- const thumbnail = await Cast(snippet!.thumbnails, Doc);
- const thumbnailMedium = await Cast(thumbnail!.medium, Doc);
- const thumbnailUrl = StrCast(thumbnailMedium!.url);
- const videoDescription = StrCast(snippet!.description);
- const pusblishDate = this.roundPublishTime(StrCast(snippet!.publishedAt))!;
- const channelTitle = StrCast(snippet!.channelTitle);
- let duration: string = '';
- let viewCount: string = '';
- if (jsonDetailList!.length !== 0) {
- const contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
- const statistics = await Cast(jsonDetailList![index].statistics, Doc);
- duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration));
- viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
- }
- index = index + 1;
- const newTemplate: VideoTemplate = {
- videoId: id,
- videoTitle: videoTitle,
- thumbnailUrl: thumbnailUrl,
- publishDate: pusblishDate,
- channelTitle: channelTitle,
- videoDescription: videoDescription,
- duration: duration,
- viewCount: viewCount,
- };
- runInAction(() => this.curVideoTemplates.push(newTemplate));
- }
- }
- }
- }
-
- _ignore = 0;
- onPreWheel = (e: React.WheelEvent) => {
- this._ignore = e.timeStamp;
- };
- onPrePointer = (e: React.PointerEvent) => {
- this._ignore = e.timeStamp;
- };
- onPostPointer = (e: React.PointerEvent) => {
- if (this._ignore !== e.timeStamp) {
- e.stopPropagation();
- }
- };
- onPostWheel = (e: React.WheelEvent) => {
- if (this._ignore !== e.timeStamp) {
- e.stopPropagation();
- }
- };
-
- /**
- * Function that submits the title entered by user on enter press.
- */
- onEnterKeyDown = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- const submittedTitle = this.YoutubeSearchElement!.value;
- this.YoutubeSearchElement!.value = '';
- this.YoutubeSearchElement!.blur();
- DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults);
- }
- };
-
- /**
- * The callback that is passed in to server, which functions as a way to
- * get videos that is returned by search. It also makes a call to server
- * to get details for the videos found.
- */
- @action
- processesVideoResults = (videos: any[]) => {
- this.searchResults = videos;
- if (this.searchResults.length > 0) {
- this.searchResultsFound = true;
- this.videoIds = '';
- videos.forEach(video => {
- if (this.videoIds === '') {
- this.videoIds = video.id.videoId;
- } else {
- this.videoIds = this.videoIds! + ', ' + video.id.videoId;
- }
- });
- //Asking for details that include duration and viewCount from server for videoIds
- DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails);
- this.backUpSearchResults(videos);
- if (this.videoClicked) {
- this.videoClicked = false;
- }
- }
- };
-
- /**
- * The callback that is given to server to process and receive returned details about the videos.
- */
- @action
- processVideoDetails = (videoDetails: any[]) => {
- this.videoDetails = videoDetails;
- this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: 'detailBackUp' });
- };
-
- /**
- * The function that stores the search results in the props document.
- */
- backUpSearchResults = (videos: any[]) => {
- this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: 'videosBackUp' });
- };
-
- /**
- * The function that filters out escaped characters returned by the api
- * in the title of the videos.
- */
- filterYoutubeTitleResult = (resultTitle: string) => {
- let processedTitle: string = resultTitle.replace(/&amp;/g, '&'); //.ReplaceAll("&amp;", "&");
- processedTitle = processedTitle.replace(/"&#39;/g, "'");
- processedTitle = processedTitle.replace(/&quot;/g, '"');
- return processedTitle;
- };
-
- /**
- * The function that converts ISO date, which is passed in, to normal date and finds the
- * difference between today's date and that date, in terms of "ago" to imitate youtube.
- */
- roundPublishTime = (publishTime: string) => {
- const date = new Date(publishTime).getTime();
- const curDate = new Date().getTime();
- const timeDif = curDate - date;
- const totalSeconds = timeDif / 1000;
- const totalMin = totalSeconds / 60;
- const totalHours = totalMin / 60;
- const totalDays = totalHours / 24;
- const totalMonths = totalDays / 30.417;
- const totalYears = totalMonths / 12;
-
- const truncYears = Math.trunc(totalYears);
- const truncMonths = Math.trunc(totalMonths);
- const truncDays = Math.trunc(totalDays);
- const truncHours = Math.trunc(totalHours);
- const truncMin = Math.trunc(totalMin);
- const truncSec = Math.trunc(totalSeconds);
-
- let pluralCase = '';
-
- if (truncYears !== 0) {
- truncYears > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncYears + ' year' + pluralCase + ' ago';
- } else if (truncMonths !== 0) {
- truncMonths > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncMonths + ' month' + pluralCase + ' ago';
- } else if (truncDays !== 0) {
- truncDays > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncDays + ' day' + pluralCase + ' ago';
- } else if (truncHours !== 0) {
- truncHours > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncHours + ' hour' + pluralCase + ' ago';
- } else if (truncMin !== 0) {
- truncMin > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncMin + ' minute' + pluralCase + ' ago';
- } else if (truncSec !== 0) {
- truncSec > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncSec + ' second' + pluralCase + ' ago';
- }
- };
-
- /**
- * The function that converts the passed in ISO time to normal duration time.
- */
- convertIsoTimeToDuration = (isoDur: string) => {
- const convertedTime = isoDur.replace(/D|H|M/g, ':').replace(/P|T|S/g, '').split(':');
-
- if (1 === convertedTime.length) {
- 2 !== convertedTime[0].length && (convertedTime[0] = '0' + convertedTime[0]), (convertedTime[0] = '0:' + convertedTime[0]);
- } else {
- for (var r = 1, l = convertedTime.length - 1; l >= r; r++) {
- 2 !== convertedTime[r].length && (convertedTime[r] = '0' + convertedTime[r]);
- }
- }
-
- return convertedTime.join(':');
- };
-
- /**
- * The function that rounds the viewCount to the nearest
- * thousand, million or billion, given a viewCount number.
- */
- abbreviateViewCount = (viewCount: number) => {
- if (viewCount < 1000) {
- return viewCount.toString();
- } else if (viewCount >= 1000 && viewCount < 1000000) {
- return Math.trunc(viewCount / 1000) + 'K';
- } else if (viewCount >= 1000000 && viewCount < 1000000000) {
- return Math.trunc(viewCount / 1000000) + 'M';
- } else if (viewCount >= 1000000000) {
- return Math.trunc(viewCount / 1000000000) + 'B';
- }
- };
-
- /**
- * The function that is called to decide on what'll be rendered by the component.
- * It renders search Results if found. If user didn't do a new search, it renders from the videoTemplates
- * generated by the backUps. If none present, renders nothing.
- */
- renderSearchResultsOrVideo = () => {
- if (this.searchResultsFound) {
- if (this.searchResults.length !== 0) {
- return (
- <ul>
- {this.searchResults.map((video, index) => {
- const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title);
- const channelTitle = video.snippet.channelTitle;
- const videoDescription = video.snippet.description;
- const pusblishDate = this.roundPublishTime(video.snippet.publishedAt);
- let duration;
- let viewCount;
- if (this.videoDetails.length !== 0) {
- duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration);
- viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount);
- }
-
- return (
- <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: 'yellow' }}>
- <img src={video.snippet.thumbnails.medium.url} />
- <span className="video_duration">{duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{filteredTitle}</span>
- <span className="channelName">{channelTitle}</span>
- <span className="viewCount">{viewCount}</span>
- <span className="publish_time">{pusblishDate}</span>
- <p className="video_description">{videoDescription}</p>
- </div>
- </div>
- </li>
- );
- })}
- </ul>
- );
- } else if (this.curVideoTemplates.length !== 0) {
- return (
- <ul>
- {this.curVideoTemplates.map((video: VideoTemplate) => {
- return (
- <li onClick={() => this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: 'yellow' }}>
- <img src={video.thumbnailUrl} />
- <span className="video_duration">{video.duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{video.videoTitle}</span>
- <span className="channelName">{video.channelTitle}</span>
- <span className="viewCount">{video.viewCount}</span>
- <span className="publish_time">{video.publishDate}</span>
- <p className="video_description">{video.videoDescription}</p>
- </div>
- </div>
- </li>
- );
- })}
- </ul>
- );
- }
- } else {
- return null;
- }
- };
-
- /**
- * Given a videoId and title, creates a new youtube embedded url, and uses that
- * to create a new video document.
- */
- @action
- embedVideoOnClick = (videoId: string, filteredTitle: string) => {
- const embeddedUrl = 'https://www.youtube.com/embed/' + videoId;
- this.selectedVideoUrl = embeddedUrl;
- const addFunction = this.props.addDocument!;
- const newVideoX = NumCast(this.props.Document.x);
- const newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height);
-
- addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
- this.videoClicked = true;
- };
-
- render() {
- const content = (
- <div className="youtubeBox-cont" style={{ width: '100%', height: '100%', position: 'absolute' }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: '100%', border: '1px solid black', padding: 5, textAlign: 'center' }} ref={e => (this.YoutubeSearchElement = e!)} />
- {this.renderSearchResultsOrVideo()}
- </div>
- );
-
- const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
-
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
- return (
- <>
- <div className={classname}>{content}</div>
- {!frozen ? null : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
- </>
- );
- }
-}
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 408903324..4ad03aab4 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,18 +1,16 @@
-import * as request from 'request-promise';
-import { Doc, Field } from '../../fields/Doc';
-import { Cast } from '../../fields/Types';
-import { Utils } from '../../Utils';
+import * as rp from 'request-promise';
+import { Doc, FieldType } from '../../fields/Doc';
import { InkData } from '../../fields/InkField';
-import { UndoManager } from '../util/UndoManager';
-import requestPromise = require('request-promise');
import { List } from '../../fields/List';
+import { Cast } from '../../fields/Types';
+import { UndoManager } from '../util/UndoManager';
type APIManager<D> = { converter: BodyConverter<D>; requester: RequestExecutor };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
type BodyConverter<D> = (data: D) => string;
-type Converter = (results: any) => Field;
-type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field; external_recommendations: any; kp_string: string[] }>;
+type Converter = (results: any) => FieldType;
+type TextConverter = (results: any, data: string) => Promise<{ keyterms: FieldType; external_recommendations: any; kp_string: string[] }>;
type BingConverter = (results: any) => Promise<{ title_vals: string[]; url_vals: string[] }>;
export type Tag = { name: string; confidence: number };
@@ -95,7 +93,7 @@ export namespace CognitiveServices {
},
};
- return request.post(options);
+ return rp.post(options);
},
};
@@ -344,10 +342,10 @@ export namespace CognitiveServices {
export namespace Appliers {
export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
console.log('vectorizing...');
- //keyterms = ["father", "king"];
+ // keyterms = ["father", "king"];
- const args = { method: 'POST', uri: Utils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
- await requestPromise.post(args).then(async wordvecs => {
+ const args = { method: 'POST', uri: ClientUtils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
+ await rp.post(args).then(async wordvecs => {
if (wordvecs) {
const indices = Object.keys(wordvecs);
console.log('successful vectorization!');
@@ -355,7 +353,7 @@ export namespace CognitiveServices {
indices.forEach((ind: any) => {
vectorValues.push(wordvecs[ind]);
});
- //ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
+ // ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
} // adds document to internal doc set
else {
console.log('unsuccessful :( word(s) not in vocabulary');
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1123bcac9..4e3496608 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -25,14 +25,13 @@ export enum DocumentType {
MAP = 'map',
DATAVIZ = 'dataviz',
LOADING = 'loading',
- SIMULATION = 'simulation', //physics simulation
+ SIMULATION = 'simulation', // physics simulation
// special purpose wrappers that either take no data or are compositions of lower level types
LINK = 'link',
IMPORT = 'import',
PRES = 'presentation',
PRESELEMENT = 'preselement',
- YOUTUBE = 'youtube',
COMPARISON = 'comparison',
GROUP = 'group',
PUSHPIN = 'pushpin',
@@ -56,11 +55,10 @@ export enum CollectionViewType {
Carousel = 'carousel',
Carousel3D = '3D Carousel',
Linear = 'linear',
- //Staff = "staff",
Map = 'map',
Grid = 'grid',
Pile = 'pileup',
StackedTimeline = 'stacked timeline',
NoteTaking = 'notetaking',
- Calendar = 'calendar'
+ Calendar = 'calendar',
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b160379df..cf397e85a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,28 +1,39 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { saveAs } from 'file-saver';
+import * as JSZip from 'jszip';
import { action, reaction, runInAction } from 'mobx';
import { basename } from 'path';
-import { OmitKeys, Utils } from '../../Utils';
+import { ClientUtils, OmitKeys } from '../../ClientUtils';
+// eslint-disable-next-line import/extensions
+import * as JSZipUtils from '../../JSZipUtils';
+import { decycle } from '../../decycler/decycler';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
import { DocData, Initializing } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { HtmlField } from '../../fields/HtmlField';
-import { InkField, PointData } from '../../fields/InkField';
-import { List } from '../../fields/List';
+import { InkDataFieldName, InkField } from '../../fields/InkField';
+import { List, ListFieldName } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
import { RichTextField } from '../../fields/RichTextField';
import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
+import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
import { SharingPermissions, inheritParentAcls } from '../../fields/util';
+import { PointData } from '../../pen-gestures/GestureTypes';
import { Upload } from '../../server/SharedMediaTypes';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { FollowLinkScript } from '../util/LinkFollower';
import { LinkManager } from '../util/LinkManager';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SerializationHelper } from '../util/SerializationHelper';
import { UndoManager, undoable } from '../util/UndoManager';
import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
@@ -30,7 +41,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionView } from '../views/collections/CollectionView';
import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { AudioBox, media_state } from '../views/nodes/AudioBox';
+import { AudioBox, mediaState } from '../views/nodes/AudioBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
import { OpenWhere } from '../views/nodes/DocumentView';
@@ -60,6 +71,7 @@ import { PresBox } from '../views/nodes/trails/PresBox';
import { PresElementBox } from '../views/nodes/trails/PresElementBox';
import { SearchBox } from '../views/search/SearchBox';
import { CollectionViewType, DocumentType } from './DocumentTypes';
+
const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
@@ -73,6 +85,7 @@ export enum FInfoFieldType {
string = 'string',
boolean = 'boolean',
number = 'number',
+ // eslint-disable-next-line @typescript-eslint/no-shadow
Doc = 'Doc',
enumeration = 'enum',
date = 'date',
@@ -83,7 +96,7 @@ export class FInfo {
description: string = '';
readOnly: boolean = false;
fieldType?: FInfoFieldType;
- values?: Field[];
+ values?: FieldType[];
filterable?: boolean = true; // can be used as a Filter in FilterPanel
// format?: string; // format to display values (e.g, decimal places, $, etc)
@@ -252,8 +265,8 @@ export class DocumentOptions {
opacity?: NUMt = new NumInfo('document opacity', false);
viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false);
dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
- _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)'
+ _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)'
_header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
_header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
_header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
@@ -321,7 +334,7 @@ export class DocumentOptions {
_label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke');
- mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
+ mediaState?: STRt = new StrInfo(`status of audio/video media document: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false);
recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)');
autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
@@ -401,7 +414,7 @@ export class DocumentOptions {
_freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)');
_freeform_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
- //BUTTONS
+ // BUTTONS
buttonText?: string;
btnType?: string;
btnList?: List<string>;
@@ -413,7 +426,7 @@ export class DocumentOptions {
switchToggle?: boolean;
badgeValue?: ScriptField;
- //LINEAR VIEW
+ // LINEAR VIEW
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');
@@ -431,7 +444,7 @@ export class DocumentOptions {
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)');
link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
- link_relationshipSizes?: List<number>; //stores number of links contained in each relationship
+ link_relationshipSizes?: List<number>; // stores number of links contained in each relationship
link_colorList?: List<string>; // colors of links corresponding to specific link relationships
followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link');
followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it');
@@ -463,7 +476,7 @@ export class DocumentOptions {
dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', false, true);
dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script', false);
clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false);
- 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
+ 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)
tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
@@ -502,8 +515,6 @@ export class DocumentOptions {
export const DocOptions = new DocumentOptions();
export namespace Docs {
- export let newAccount: boolean = false;
-
export namespace Prototypes {
type LayoutSource = { LayoutString: (key: string) => string };
type PrototypeTemplate = {
@@ -647,12 +658,6 @@ export namespace Docs {
},
],
[
- DocumentType.YOUTUBE,
- {
- layout: { view: YoutubeBox, dataField: defaultDataKey },
- },
- ],
- [
DocumentType.LABEL,
{
layout: { view: LabelBox, dataField: 'title' },
@@ -670,7 +675,7 @@ export namespace Docs {
layout_nativeDimEditable: true,
layout_hideDecorationTitle: true,
systemIcon: 'BsCalculatorFill',
- }, ///systemIcon: 'BsSuperscript' + BsSubscript
+ }, // systemIcon: 'BsSuperscript' + BsSubscript
},
],
[
@@ -850,7 +855,7 @@ export namespace Docs {
// fetch the actual prototype documents from the server
const actualProtos = await DocServer.GetRefFields(prototypeIds);
// update this object to include any default values: DocumentOptions for all prototypes
- prototypeIds.map(id => {
+ prototypeIds.forEach(id => {
const existing = actualProtos[id] as Doc;
const type = id.replace(suffix, '') as DocumentType;
// get or create prototype of the specified type...
@@ -908,7 +913,7 @@ export namespace Docs {
if (!template) {
return undefined;
}
- const layout = template.layout;
+ const { layout } = template;
// create title
const upper = suffix.toUpperCase();
const title = prototypeId.toUpperCase().replace(upper, `_${upper}`);
@@ -928,7 +933,7 @@ export namespace Docs {
};
Object.entries(options)
.filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@'))
- .map(pair => {
+ .forEach(pair => {
if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) {
(options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1));
}
@@ -960,7 +965,9 @@ 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, noView?: boolean) {
+ // eslint-disable-next-line default-param-last
+ function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) {
+ const placeholderDoc = placeholderDocIn;
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, '^_');
@@ -968,7 +975,7 @@ export namespace Docs {
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 = ClientUtils.CurrentUserEmail();
dataProps.author_date = new DateField();
if (fieldKey) {
dataProps[`${fieldKey}_modificationDate`] = new DateField();
@@ -988,7 +995,7 @@ export namespace Docs {
}
if (!noView) {
- const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail };
+ const viewFirstProps: { [id: string]: any } = { author: ClientUtils.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
@@ -998,7 +1005,9 @@ export namespace Docs {
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]));
+ .forEach(key => {
+ dataDoc[key] = viewDoc[key] = placeholderDoc[key];
+ });
} else {
viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
}
@@ -1017,6 +1026,7 @@ export namespace Docs {
return dataDoc;
}
+ // eslint-disable-next-line default-param-last
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
@@ -1035,18 +1045,16 @@ export namespace Docs {
* @param fieldKey the field that the compiled script is written into.
* @returns the Scripting Doc
*/
+ // eslint-disable-next-line default-param-last
export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
+ return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
}
+ // eslint-disable-next-line default-param-last
export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc);
}
- export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc);
- }
-
export function WebCamDocument(url: string, options: DocumentOptions = {}) {
return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options);
}
@@ -1059,6 +1067,7 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options);
}
+ // eslint-disable-next-line default-param-last
export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc);
}
@@ -1072,7 +1081,7 @@ export namespace Docs {
}
export function LoadingDocument(file: File | string, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
+ return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file === 'string' ? file : file.name, ...options }, undefined, '');
}
export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
@@ -1101,6 +1110,7 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
+ // eslint-disable-next-line default-param-last
export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
const linkDoc = InstanceFromProto(
Prototypes.get(DocumentType.LINK),
@@ -1124,7 +1134,7 @@ export namespace Docs {
options: DocumentOptions = {},
strokeWidth = ActiveInkWidth(),
color = ActiveInkColor(),
- stroke_bezier = ActiveInkBezierApprox(),
+ strokeBezier = ActiveInkBezierApprox(),
fillColor = ActiveFillColor(),
arrowStart = ActiveArrowStart(),
arrowEnd = ActiveArrowEnd(),
@@ -1138,7 +1148,7 @@ export namespace Docs {
I.fillColor = fillColor;
I.stroke = new InkField(points);
I.stroke_width = strokeWidth;
- I.stroke_bezier = stroke_bezier;
+ I.stroke_bezier = strokeBezier;
I.stroke_startMarker = arrowStart;
I.stroke_endMarker = arrowEnd;
I.stroke_dash = dash;
@@ -1148,12 +1158,13 @@ export namespace Docs {
I.defaultDoubleClick = 'ignore';
I.author_date = new DateField();
I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
- //I['acl-Override'] = SharingPermissions.Unset;
+ // I['acl-Override'] = SharingPermissions.Unset;
I[Initializing] = false;
return ink;
}
+ // eslint-disable-next-line default-param-last
export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
const width = options._width || undefined;
const height = options._height || undefined;
@@ -1169,7 +1180,7 @@ export namespace Docs {
const nwid = options._nativeWidth || undefined;
const nhght = options._nativeHeight || undefined;
if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
- return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -1324,10 +1335,10 @@ export namespace Docs {
const doc = DockDocument(
configs.map(c => c.doc),
JSON.stringify(layoutConfig),
- Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
+ ClientUtils.CurrentUserEmail() === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
id
);
- configs.map(c => {
+ configs.forEach(c => {
Doc.SetContainer(c.doc, doc);
inheritParentAcls(doc, c.doc, false);
});
@@ -1345,17 +1356,18 @@ export namespace Docs {
}
export namespace DocUtils {
- function matchFieldValue(doc: Doc, key: string, value: any): boolean {
- const hasFunctionFilter = Utils.HasFunctionFilter(value);
+ function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean {
+ let value = valueIn;
+ const hasFunctionFilter = ClientUtils.HasFunctionFilter(value);
if (hasFunctionFilter) {
return hasFunctionFilter(StrCast(doc[key]));
}
if (key === LinkedTo) {
// links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("..."))
const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
- const matchLink = (value: string, anchor: Doc) => {
- const linkedToExp = (value ?? '').split('=');
- if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+ const matchLink = (val: string, anchor: Doc) => {
+ const linkedToExp = (val ?? '').split('=');
+ if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
};
// prettier-ignore
@@ -1365,7 +1377,7 @@ export namespace DocUtils {
matchLink(value,DocCast(link.link_anchor_2)) ));
}
if (typeof value === 'string') {
- value = value.replace(`,${Utils.noRecursionHack}`, '');
+ value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
}
const fieldVal = doc[key];
// prettier-ignore
@@ -1377,7 +1389,7 @@ export namespace DocUtils {
if (vals.length) {
return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
- return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
/**
* @param docs
@@ -1410,7 +1422,9 @@ export namespace DocUtils {
if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
return false;
}
- for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) {
+ const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]);
+ // eslint-disable-next-line no-restricted-syntax
+ for (const facetKey of facetKeys) {
const facet = filterFacets[facetKey];
// facets that match some value in the field of the document (e.g. some text field)
@@ -1431,8 +1445,8 @@ export namespace DocUtils {
if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
- const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
- const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
+ const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length;
+ const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
const satisfiesMatchFacets = !matches.length
? true
: matches.some(value => {
@@ -1441,20 +1455,18 @@ export namespace DocUtils {
const allKeys = Array.from(Object.keys(d));
allKeys.push(...Object.keys(Doc.GetProto(d)));
const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
- return keys.some(key => Field.toString(d[key] as Field).includes(value));
+ return keys.some(key => Field.toString(d[key] as FieldType).includes(value));
}
- return Field.toString(d[facetKey] as Field).includes(value);
+ return Field.toString(d[facetKey] as FieldType).includes(value);
});
// if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
if (parentCollection?.childFilters_boolean === 'OR') {
if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
}
// if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
- else {
- if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
- }
+ else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
}
- return parentCollection?.childFilters_boolean === 'OR' ? false : true;
+ return parentCollection?.childFilters_boolean !== 'OR';
})
: childDocs;
const rangeFilteredDocs = filteredDocs.filter(d => {
@@ -1464,7 +1476,7 @@ export namespace DocUtils {
const max = Number(childFiltersByRanges[i + 2]);
const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
if (val === undefined) {
- //console.log("Should 'undefined' pass range filter or not?")
+ // console.log("Should 'undefined' pass range filter or not?")
} else if (val < min || val > max) return false;
}
return true;
@@ -1472,10 +1484,10 @@ export namespace DocUtils {
return rangeFilteredDocs;
}
- export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
+ export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
- broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1));
+ broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore
return DocUtils.ActiveRecordings.map(audio => {
const sourceDoc = getSourceDoc();
return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
@@ -1486,17 +1498,17 @@ export namespace DocUtils {
if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
if (target.doc === Doc.UserDoc()) return undefined;
- const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
- if (showPopup) {
+ const makeLink = action((linkDoc: Doc, showAt?: number[]) => {
+ if (showAt) {
LinkManager.Instance.currentLink = linkDoc;
TaskCompletionBox.textDisplayed = 'Link Created';
- TaskCompletionBox.popupX = showPopup[0];
- TaskCompletionBox.popupY = showPopup[1] - 33;
+ TaskCompletionBox.popupX = showAt[0];
+ TaskCompletionBox.popupY = showAt[1] - 33;
TaskCompletionBox.taskCompleted = true;
- LinkDescriptionPopup.Instance.popupX = showPopup[0];
- LinkDescriptionPopup.Instance.popupY = showPopup[1];
+ LinkDescriptionPopup.Instance.popupX = showAt[0];
+ LinkDescriptionPopup.Instance.popupY = showAt[1];
LinkDescriptionPopup.Instance.display = true;
const rect = document.body.getBoundingClientRect();
@@ -1510,7 +1522,9 @@ export namespace DocUtils {
}
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2500
);
}
@@ -1550,7 +1564,7 @@ export namespace DocUtils {
export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
scripts &&
- Object.keys(scripts).map(key => {
+ Object.keys(scripts).forEach(key => {
const script = scripts[key];
if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
(key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
@@ -1573,7 +1587,7 @@ export namespace DocUtils {
funcs &&
Object.keys(funcs)
.filter(key => !key.endsWith('-setter'))
- .map(key => {
+ .forEach(key => {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
const setFunc = Cast(funcs[key + '-setter'], 'string', null);
@@ -1602,6 +1616,7 @@ export namespace DocUtils {
return doc;
}
export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
}
@@ -1649,7 +1664,7 @@ export namespace DocUtils {
* @returns
*/
export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined;
+ let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
if (type.indexOf('image') !== -1) {
ctor = Docs.Create.ImageDocument;
if (!options._width) options._width = 300;
@@ -1672,7 +1687,7 @@ export namespace DocUtils {
if (!options._width) options._width = 400;
if (!options._height) options._height = ((options._width as number) * 1200) / 927;
}
- //TODO:al+glr
+ // TODO:al+glr
// if (type.indexOf("map") !== -1) {
// ctor = Docs.Create.MapDocument;
// if (!options._width) options._width = 800;
@@ -1695,6 +1710,7 @@ export namespace DocUtils {
});
}
ctor = Docs.Create.WebDocument;
+ // eslint-disable-next-line no-param-reassign
options = { ...options, _width: 400, _height: 512, title: path };
}
@@ -1706,12 +1722,12 @@ export namespace DocUtils {
.filter(btnDoc => !btnDoc.hidden)
.map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
- .map((dragDoc, i) => ({
+ .map(dragDoc => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
- event: undoable((args: { x: number; y: number }) => {
+ event: undoable(() => {
const newDoc = DocUtils.copyDragFactory(dragDoc);
if (newDoc) {
- newDoc.author = Doc.CurrentUserEmail;
+ newDoc.author = ClientUtils.CurrentUserEmail();
newDoc.x = x;
newDoc.y = y;
EquationBox.SelectOnLoad = newDoc[Id];
@@ -1732,9 +1748,9 @@ export namespace DocUtils {
!simpleMenu &&
ContextMenu.Instance.addItem({
description: 'Styled Notes',
- subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
+ subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({
description: ':' + StrCast(note.title),
- event: undoable((args: { x: number; y: number }) => {
+ event: undoable(() => {
const textDoc = Docs.Create.TextDocument('', {
_width: 200,
x,
@@ -1757,12 +1773,12 @@ export namespace DocUtils {
.filter(btnDoc => !btnDoc.hidden)
.map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
- .map((dragDoc, i) => ({
+ .map(dragDoc => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
- event: undoable((args: { x: number; y: number }) => {
+ event: undoable(() => {
const newDoc = DocUtils.delegateDragFactory(dragDoc);
if (newDoc) {
- newDoc.author = Doc.CurrentUserEmail;
+ newDoc.author = ClientUtils.CurrentUserEmail();
newDoc.x = x;
newDoc.y = y;
EquationBox.SelectOnLoad = newDoc[Id];
@@ -1800,13 +1816,13 @@ export namespace DocUtils {
batch.end();
return doc;
}
- export function findTemplate(templateName: string, type: string, signature: string) {
+ export function findTemplate(templateName: string, type: 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 userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], 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 userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data);
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
const allTemplates = iconViews
.concat(templBtns)
.concat(noteTypes)
@@ -1816,14 +1832,21 @@ export namespace DocUtils {
.filter(doc => doc.isTemplateDoc);
// bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
// first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc));
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate &&
+ allTemplates.forEach(tempDoc => {
+ StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc);
+ });
+ !docLayoutTemplate &&
+ allTemplates.forEach(tempDoc => {
+ StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc);
+ });
return docLayoutTemplate;
}
export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
const templateName = templateSignature.replace(/\(.*\)/, '');
- doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title);
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
+ doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
+ // eslint-disable-next-line no-param-reassign
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
const customName = 'layout_' + templateSignature;
const _width = NumCast(doc._width);
@@ -1859,14 +1882,15 @@ export namespace DocUtils {
}
}
export function iconify(doc: Doc) {
- const layout_fieldKey = Cast(doc.layout_fieldKey, 'string', null);
+ const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null);
DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
- if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') doc.deiconifyLayout = layout_fieldKey.replace('layout_', '');
+ if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', '');
}
export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
runInAction(() => {
- docList.forEach((d, i) => {
+ docList.forEach((doc, i) => {
+ const d = doc;
DocUtils.iconify(d);
d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
@@ -1880,6 +1904,7 @@ export namespace DocUtils {
newCollection._width = newCollection._height = size * 2;
return newCollection;
}
+ return undefined;
}
export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
@@ -1921,7 +1946,7 @@ export namespace DocUtils {
_timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
});
Doc.AddDocToList(context, annotationField, pushpin);
- const pushpinLink = DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
+ DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
doc._timecodeToShow = undefined;
return pushpin;
}
@@ -1948,23 +1973,24 @@ export namespace DocUtils {
// }
function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
- var dd = degrees + minutes / 60 + seconds / (60 * 60);
+ let dd = degrees + minutes / 60 + seconds / (60 * 60);
if (direction === 'S' || direction === 'W') {
- dd = dd * -1;
+ dd *= -1;
} // Don't do anything for N or E
return dd;
}
- export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+ export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+ const proto = protoIn;
if (Upload.isImageInformation(result)) {
const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
- proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto['data-nativeOrientation']) >= 5) {
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+ proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto.data_nativeOrientation) >= 5) {
+ proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
}
proto.data_exif = JSON.stringify(result.exifData?.data);
proto.data_contentSize = result.contentSize;
@@ -2033,7 +2059,7 @@ export namespace DocUtils {
const generatedDocuments: Doc[] = [];
Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
const {
- source: { newFilename, originalFilename, mimetype },
+ source: { newFilename, mimetype },
result,
} = upfiles.lastElement();
if ((result as any).message) {
@@ -2061,12 +2087,9 @@ export namespace DocUtils {
const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
- for (const {
- source: { newFilename, mimetype },
- result,
- } of upfiles) {
+ upfiles.forEach(({ source: { newFilename, mimetype }, result }) => {
newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
- }
+ });
return generatedDocuments;
}
@@ -2091,34 +2114,119 @@ export namespace DocUtils {
export function copyDragFactory(dragFactory: Doc) {
if (!dragFactory) return undefined;
const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
- 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 && dragFactory.dragFactory_count !== undefined) {
+ dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+ Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true);
}
return ndoc;
}
export function delegateDragFactory(dragFactory: Doc) {
const ndoc = Doc.MakeDelegateWithProto(dragFactory);
- if (ndoc && dragFactory['dragFactory_count'] !== undefined) {
- dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1;
- Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString();
+ if (ndoc && dragFactory.dragFactory_count !== undefined) {
+ dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+ Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
}
return ndoc;
}
+
+ export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+ const { clone, map, linkMap } = await Doc.MakeClone(doc);
+ const proms = new Set<string>();
+ function replacer(key: any, value: any) {
+ if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
+ if (value?.__type === 'image') {
+ const extension = value.url.replace(/.*\./, '');
+ proms.add(value.url.replace('.' + extension, '_o.' + extension));
+ return SerializationHelper.Serialize(new ImageField(value.url));
+ }
+ if (value?.__type === 'pdf') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new PdfField(value.url));
+ }
+ if (value?.__type === 'audio') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new AudioField(value.url));
+ }
+ if (value?.__type === 'video') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new VideoField(value.url));
+ }
+ if (
+ value instanceof Doc ||
+ value instanceof ScriptField ||
+ value instanceof RichTextField ||
+ value instanceof InkField ||
+ value instanceof CsvField ||
+ value instanceof WebField ||
+ value instanceof DateField ||
+ value instanceof ProxyField ||
+ value instanceof ComputedField
+ ) {
+ return SerializationHelper.Serialize(value);
+ }
+ if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' };
+ return value;
+ }
+
+ const docs: { [id: string]: any } = {};
+ const links: { [id: string]: any } = {};
+ Array.from(map.entries()).forEach(f => {
+ docs[f[0]] = f[1];
+ });
+ Array.from(linkMap.entries()).forEach(l => {
+ links[l[0]] = l[1];
+ });
+ const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
+
+ const zip = new JSZip();
+ let count = 0;
+ const promArr = Array.from(proms)
+ .filter(url => url?.startsWith('/files'))
+ .map(url => url.replace('/', '')); // window.location.origin));
+ console.log(promArr.length);
+ if (!promArr.length) {
+ zip.file('docs.json', jsonDocs);
+ zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+ } else
+ promArr.forEach((url, i) => {
+ // loading a file and add it in a zip file
+ JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
+ if (err) throw err; // or handle the error
+ // // Generate a directory within the Zip file structure
+ // const assets = zip.folder("assets");
+ // assets.file(filename, data, {binary: true});
+ const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
+ zip.file(assetPathOnServer, data, { binary: true });
+ console.log(' => ' + url);
+ if (++count === promArr.length) {
+ zip.file('docs.json', jsonDocs);
+ zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
+ }
+ });
+ });
+ }
}
ScriptingGlobals.add('Docs', Docs);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function makeDelegate(proto: any) {
const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
return d;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
- const link_anchor_1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
- const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
+ const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
+ const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
const relation = link.link_relationship || 'to';
- return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`;
+ return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`;
});
diff --git a/src/client/goldenLayout.d.ts b/src/client/goldenLayout.d.ts
index b50240563..26e8ba356 100644
--- a/src/client/goldenLayout.d.ts
+++ b/src/client/goldenLayout.d.ts
@@ -1,3 +1,2 @@
-
declare const GoldenLayout: any;
-export = GoldenLayout; \ No newline at end of file
+export = GoldenLayout;
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 02879e3c4..28c00644f 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,18 +1,31 @@
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/no-array-index-key */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
-import { PresBox } from '../views/nodes/trails';
import { OverlayView } from '../views/OverlayView';
+import { PresBox } from '../views/nodes/trails';
import { DocumentManager } from './DocumentManager';
-import { Docs } from '../documents/Documents';
-import { nullAudio } from '../../fields/URLField';
@observer
export class BranchingTrailManager extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: BranchingTrailManager;
+ // stack of the history
+ @observable private slideHistoryStack: String[] = [];
+ @observable private containsSet: Set<String> = new Set<String>();
+ // docId to Doc map
+ @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+ // prev pres to copmare with
+ @observable private prevPresId: String | null = null;
+ @action setPrevPres = action((newId: String | null) => {
+ this.prevPresId = newId;
+ });
+
constructor(props: any) {
super(props);
makeObservable(this);
@@ -22,7 +35,7 @@ export class BranchingTrailManager extends React.Component {
}
setupUi = () => {
- OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
+ OverlayView.Instance.addWindow(<BranchingTrailManager />, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
// OverlayView.Instance.forceUpdate();
console.log(OverlayView.Instance);
// let hi = Docs.Create.TextDocument("beee", {
@@ -36,23 +49,11 @@ export class BranchingTrailManager extends React.Component {
console.log(DocumentManager._overlayViews);
};
- // stack of the history
- @observable private slideHistoryStack: String[] = [];
@action setSlideHistoryStack = action((newArr: String[]) => {
this.slideHistoryStack = newArr;
});
- @observable private containsSet: Set<String> = new Set<String>();
-
- // prev pres to copmare with
- @observable private prevPresId: String | null = null;
- @action setPrevPres = action((newId: String | null) => {
- this.prevPresId = newId;
- });
-
- // docId to Doc map
- @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
-
+ // eslint-disable-next-line react/sort-comp
observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
const presId = pres.Document[Id];
if (this.prevPresId === presId) {
@@ -106,7 +107,7 @@ export class BranchingTrailManager extends React.Component {
if (this.slideHistoryStack.length === 0) {
Doc.UserDoc().isBranchingMode = false;
}
- //PresBox.NavigateToTarget(targetDoc, targetDoc);
+ // PresBox.NavigateToTarget(targetDoc, targetDoc);
};
@computed get trailBreadcrumbs() {
@@ -116,11 +117,11 @@ export class BranchingTrailManager extends React.Component {
const [presId, targetDocId] = info.split(',');
const doc = this.docIdToDocMap.get(targetDocId);
if (!doc) {
- return <></>;
+ return null;
}
return (
<span key={targetDocId}>
- <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+ <button type="button" key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
{presId.slice(0, 3) + ':' + doc.title}
</button>
-{'>'}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6e9094b3a..8e3936d34 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,4 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TextField } from '@mui/material';
+import { Button } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -6,21 +12,16 @@ import Select from 'react-select';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { StrCast } from '../../fields/Types';
+import { Docs } from '../documents/Documents';
import { DictationOverlay } from '../views/DictationOverlay';
import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
import { DocumentView } from '../views/nodes/DocumentView';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './CalendarManager.scss';
import { DocumentManager } from './DocumentManager';
import { SelectionManager } from './SelectionManager';
import { SettingsManager } from './SettingsManager';
-// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
-import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button } from 'browndash-components';
-import { Docs } from '../documents/Documents';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
// import 'react-date-range/dist/styles.css';
// import 'react-date-range/dist/theme/default.css';
@@ -47,12 +48,12 @@ const formatCalendarDateToString = (calendarDate: any) => {
@observer
export class CalendarManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CalendarManager;
@observable private isOpen = false;
@observable private targetDoc: Doc | undefined = undefined; // the target document
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@@ -83,10 +84,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
this.creationType = type;
};
- public open = (target?: DocumentView, target_doc?: Doc) => {
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
console.log('hi');
runInAction(() => {
- this.targetDoc = target_doc || target?.Document;
+ this.targetDoc = targetDoc || target?.Document;
this.targetDocView = target;
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
@@ -117,7 +118,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
handleSelectChange = (option: any) => {
if (option) {
- let selectOpt = option as CalendarSelectOptions;
+ const selectOpt = option as CalendarSelectOptions;
this.selectedExistingCalendarOption = selectOpt;
this.calendarName = selectOpt.value; // or label
}
@@ -136,7 +137,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
// TODO: Make undoable
private addToCalendar = () => {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
console.log(targetDoc);
@@ -159,7 +160,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
} else {
// find existing calendar based on selected name (should technically always find one)
- const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ const existingCalendar = this.existingCalendars.find(findCal => StrCast(findCal.title) === this.calendarName);
if (existingCalendar) calendar = existingCalendar;
else {
this.errorMessage = 'Must select an existing calendar';
@@ -203,14 +204,12 @@ export class CalendarManager extends ObservableReactComponent<{}> {
if (docs.length) {
docs.forEach(doc => doc && Doc.BrushDoc(doc));
this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
}
})}
onPointerLeave={action(() => {
if (docs.length) {
docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
}
})}>
{contents}
@@ -252,11 +251,9 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@computed
get calendarInterface() {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
- const currentDate = new Date();
-
return (
<div
className="calendar-interface"
@@ -268,10 +265,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
<b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
</p>
<div className="creation-type-container">
- <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('new-calendar')}>
+ <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('new-calendar')}>
Add to New Calendar
</div>
- <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+ <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('existing-calendar')}>
Add to Existing calendar
</div>
</div>
@@ -317,7 +314,8 @@ export class CalendarManager extends ObservableReactComponent<{}> {
color: StrCast(Doc.UserDoc().userColor),
width: '100%',
}),
- }}></Select>
+ }}
+ />
)}
</div>
<div className="description-container">
@@ -351,6 +349,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 8451357ef..a03c80f2a 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { addStyleSheet } from '../../Utils';
+import { addStyleSheet } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
import { LightboxView } from '../views/LightboxView';
@@ -13,18 +15,22 @@ import { SelectionManager } from './SelectionManager';
@observer
export class CaptureManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CaptureManager;
static _settingsStyle = addStyleSheet();
@observable _document: any = undefined;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
+ // eslint-disable-next-line react/sort-comp
constructor(props: {}) {
super(props);
makeObservable(this);
CaptureManager.Instance = this;
}
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action((doc: Doc) => {
this.isOpen = true;
this._document = doc;
@@ -99,15 +105,15 @@ export class CaptureManager extends React.Component<{}> {
<div className="capture-interface">
<div className="capture-t1">
<div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}></div>
+ <div className="recordButtonInner" style={{}} />
</div>
Conversation Capture
</div>
- <div className="capture-t2"></div>
+ <div className="capture-t2" />
{this.visibilityContent}
{this.linksContent}
<div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ <FontAwesomeIcon icon="times" color="black" size="lg" />
</div>
{this.closeButtons}
</div>
@@ -119,11 +125,10 @@ export class CaptureManager extends React.Component<{}> {
<MainViewModal
contents={this.captureInterface}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
overlayStyle={{ background: 'black' }}
- overlayDisplayedOpacity={0.6}
/>
);
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 081115879..65dce34a5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,6 @@
import { observable, reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
-import { OmitKeys, Utils } from "../../Utils";
+import { ClientUtils, OmitKeys } from "../../ClientUtils";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { DocData } from "../../fields/DocSymbols";
import { InkTool } from "../../fields/InkField";
@@ -12,7 +12,7 @@ import { ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
import { WebField, nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
-import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { Gestures } from "../../pen-gestures/GestureTypes";
import { DocServer } from "../DocServer";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
@@ -20,20 +20,21 @@ import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
import { Colors } from "../views/global/globalEnums";
-import { media_state } from "../views/nodes/AudioBox";
+import { mediaState } from "../views/nodes/AudioBox";
import { OpenWhere } from "../views/nodes/DocumentView";
import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { LabelBox } from "../views/nodes/LabelBox";
import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { DragManager, dropActionType } from "./DragManager";
+import { DragManager } from "./DragManager";
+import { dropActionType } from "./DropActionTypes";
import { MakeTemplate } from "./DropConverter";
import { FollowLinkScript } from "./LinkFollower";
-import { LinkManager } from "./LinkManager";
+import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
import { ColorScheme } from "./SettingsManager";
import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { ImageBox } from "../views/nodes/ImageBox";
interface Button {
// DocumentOptions fields a button can set
@@ -60,12 +61,13 @@ interface Button {
subMenu?: Button[];
}
+// eslint-disable-next-line import/no-mutable-exports
export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
// initializes experimental advanced template views - slideView, headerView
static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
- const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
+ const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc));
const reqdOpts:DocumentOptions = {
title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
_dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
@@ -85,11 +87,12 @@ export class CurrentUserUtils {
{ opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
});
const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -107,13 +110,14 @@ export class CurrentUserUtils {
const reqdClickList = reqdTempOpts.map(opts => {
const title = opts.opts.title?.toString();
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined;
const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
});
const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -126,11 +130,12 @@ export class CurrentUserUtils {
{ title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}];
const reqdNoteList = [...reqdTempOpts.map(opts => {
const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
- const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+ const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
}), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
static setupUserTemplates(doc: Doc, field="template_user") {
@@ -138,6 +143,7 @@ export class CurrentUserUtils {
const reqdUserList = DocListCast(tempUsers?.data);
const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
}
@@ -159,24 +165,26 @@ export class CurrentUserUtils {
const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
+ const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
+ layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+ });
+ const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
+ const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
+
const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
const title = "icon" + (type ? "_" + type : "");
const curIcon = DocCast(templateIconsDoc[title]);
- let creator = labelBox;
- switch (opts.iconTemplate) {
- case DocumentType.IMG : creator = imageBox; break;
- case DocumentType.FONTICON: creator = fontBox; break;
- }
+ const creator = (() => { switch (opts.iconTemplate) {
+ case DocumentType.IMG : return imageBox;
+ case DocumentType.FONTICON: return fontBox;
+ default: return labelBox;
+ }})();
const allopts = {isSystem: true, onClickScriptDisable: "never", ...opts, title};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
{onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
};
- const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
- layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
- });
- const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
- const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
const iconTemplates = [
makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}),
makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
@@ -188,9 +196,9 @@ export class CurrentUserUtils {
makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.BUTTON,"title", { iconTemplate:DocumentType.FONTICON}),
- //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+ // nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
- //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
+ // makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
].filter(d => d).map(d => d!);
DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
}
@@ -241,14 +249,15 @@ export class CurrentUserUtils {
Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
], {...opts, title: "Slide View Template"}));
const plotlyApi = () => {
- var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+ let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly");
if (!plotly) {
- const plotly = Docs.Create.TextDocument(
+ plotly = Docs.Create.TextDocument(
`await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
Plotly.newPlot(dashDiv.id, [ --DOCDATA-- ])`
, {title: "@plotly", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(plotly);
}
+ return plotly;
}
const plotlyView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -280,10 +289,10 @@ export class CurrentUserUtils {
return slide;
}
const mermaidsApi = () => {
- var mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+ let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids");
if (!mermaids) {
- const mermaids = Docs.Create.TextDocument(
- `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid\@10.8.0/dist/mermaid.esm.min.mjs")).default;
+ mermaids = Docs.Create.TextDocument(
+ `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
window["callb"] = (x) => {
alert(x);
}
@@ -301,6 +310,7 @@ export class CurrentUserUtils {
, {title: "@mermaids", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(mermaids);
}
+ return mermaids;
}
const mermaidsView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -333,7 +343,7 @@ pie title Minerals in my tap water
slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
- const apis = [plotlyApi(), mermaidsApi()]
+ plotlyApi(); mermaidsApi();
const emptyThings:{key:string, // the field name where the empty thing will be stored
opts:DocumentOptions, // the document options that are required for the empty thing
funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing
@@ -406,7 +416,7 @@ pie title Minerals in my tap water
/// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
- const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+ const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
_width: 60, _height: 60, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
@@ -450,7 +460,7 @@ pie title Minerals in my tap water
this.setupLeftSidebarPanel(doc);
const myLeftSidebarMenu = DocCast(doc[field]);
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 btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
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,
@@ -465,83 +475,9 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
- // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
- static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
- const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, isSystem: true, _chromeHidden: true,};
- DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
- }
-
- // Sets up mobile buttons for inside mobile menu
- static setupMobileButtons(doc?: Doc, buttons?: string[]) {
- return [];
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
- { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
- { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
- { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
- { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
- { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
- { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
- ];
- // returns a list of mobile buttons
- return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
- this.mobileButton({
- title: data.title,
- _lockedPosition: true,
- onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- backgroundColor: data.backgroundColor, isSystem: true
- },
- [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", isSystem: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
- );
- }
-
- // sets up the main document for the mobile button
- static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
- ...opts,
- _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
- layout_borderRounding: "5px", layout_boxShadow: "0 0", isSystem: true
- }) as any as Doc
-
- // sets up the text container for the information contained within the mobile button
- static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
- ...opts,
- _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
- backgroundColor: "rgba(0,0,0,0)", layout_borderRounding: "0", layout_boxShadow: "0 0", ignoreClick: true, isSystem: true
- }) as any as Doc
-
- // Sets up the title of the button
- static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
- ...opts,
- title: buttonTitle, _text_fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", isSystem: true
- }) as any as Doc
-
- // Sets up the description of the button
- static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
- ...opts,
- title: "info", _text_fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, isSystem: true
- }) as any as Doc
-
-
-
- static setupMobileInkingDoc(userDoc: Doc) {
- return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", isSystem: true });
- }
-
- static setupMobileUploadDoc(userDoc: Doc) {
- // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
- const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
- title: "Upload Images From the Web", _lockedPosition: true, isSystem: true
- });
- const uploadDoc = Docs.Create.StackingDocument([], {
- title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, isSystem: true, _chromeHidden: true,
- });
- return Docs.Create.StackingDocument([webDoc, uploadDoc], {
- _width: screen.width, _lockedPosition: true, title: "Upload", _layout_autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", isSystem: true, _chromeHidden: true,
- });
- }
-
/// 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), {
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.SearchDocument(opts), {
dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed,
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
@@ -552,7 +488,7 @@ pie title Minerals in my tap water
const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
- //doc.myUserBtns = new PrefetchProxy(userBtns);
+ // doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.",
@@ -563,7 +499,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar dashboard pane
static setupDashboards(doc: Doc, field:string) {
- var myDashboards = DocCast(doc[field]);
+ let myDashboards = DocCast(doc[field]);
const newDashboard = `createNewDashboard()`;
@@ -572,9 +508,9 @@ pie title Minerals in my tap water
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
- const contextMenuScripts = [/*newDashboard*/] as string[];
- const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
- const contextMenuIcons = [/*"plus"*/] as string[];
+ const contextMenuScripts = [/* newDashboard */] as string[];
+ const contextMenuLabels = [/* "Create New Dashboard" */] as string[];
+ const contextMenuIcons = [/* "plus" */] as string[];
const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(this)`, 'removeDashboard(this)', 'resetDashboard(this)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
const childContextMenuLabels = ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
@@ -605,7 +541,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
- var myFilesystem = DocCast(doc[field]);
+ const myFilesystem = DocCast(doc[field]);
const newFolderOpts: DocumentOptions = {
_forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
@@ -650,7 +586,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar panel view of the UserDoc
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
- _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
+ _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail() +"-view",
layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
@@ -671,14 +607,14 @@ pie title Minerals in my tap water
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _dropPropertiesToRemove: new List<string>([ "layout_hideContextMenu"]),
- /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
+ /* _nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
})
/// initializes the required buttons in the expanding button menu at the bottom of the Dash window
static setupDockedButtons(doc: Doc, field="myDockedBtns") {
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) =>
- DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ??
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
@@ -694,8 +630,8 @@ pie title Minerals in my tap water
title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.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);
}
@@ -752,9 +688,9 @@ pie title Minerals in my tap water
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
- { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
- { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
- { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
{ title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
@@ -773,7 +709,7 @@ pie title Minerals in my tap water
return [
{ title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }},
{ title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}},
- { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
+ { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
static videoTools() {
@@ -835,16 +771,16 @@ pie title Minerals in my tap water
static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
const menuBtnDoc = DocListCast(menuDoc?.data).find( doc => doc.title === params.title);
- const subMenu = params.subMenu;
+ const {subMenu} = params;
if (!subMenu) { // button does not have a sub menu
return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
}
// 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,
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
- const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+ const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
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);
@@ -866,17 +802,17 @@ pie title Minerals in my tap water
Doc.UserDoc().workspaceReplayingState = undefined;
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
- DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ??
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
{ opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
{ opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
{ opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}},
- { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+ { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
];
const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
const dockBtnsReqdOpts:DocumentOptions = {
@@ -890,12 +826,14 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
}
- /// The database of all links on all documents
+ static newAccount: boolean = false;
+
+ // The database of all links on all documents
static setupLinkDocs(doc: Doc, linkDatabaseId: string) {
- if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
+ if (!(CurrentUserUtils.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
const linkDocs = new Doc(linkDatabaseId, true);
- linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
- linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail();
+ linkDocs.author = ClientUtils.CurrentUserEmail();
linkDocs.isSystem = true;
linkDocs.data = new List<Doc>([]);
linkDocs["acl-Guest"] = SharingPermissions.Augment;
@@ -949,16 +887,17 @@ pie title Minerals in my tap water
/// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
/// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
/// whether to revert to "default" values, or to leave them as the user/system last set them.
- static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ const doc = docIn;
DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
- reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data_modificationDate"]),
+ reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
- const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
+ const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || [];
+ SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
}, { fireImmediately: true });
doc.isSystem ?? (doc.isSystem = true);
- doc.title ?? (doc.title = Doc.CurrentUserEmail);
+ doc.title ?? (doc.title = ClientUtils.CurrentUserEmail());
Doc.noviceMode ?? (Doc.noviceMode = true);
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
@@ -971,7 +910,7 @@ pie title Minerals in my tap water
doc.activeFillColor ?? (doc.activeFillColor = "");
doc.activeArrowStart ?? (doc.activeArrowStart = "");
doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
- doc.activeDash ?? (doc.activeDash == "0");
+ doc.activeDash ?? (doc.activeDash === "0");
doc.fontSize ?? (doc.fontSize = "12px");
doc.fontFamily ?? (doc.fontFamily = "Arial");
doc.fontColor ?? (doc.fontColor = "black");
@@ -988,15 +927,14 @@ pie title Minerals in my tap water
this.setupLinkDocs(doc, linkDatabaseId);
this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
- this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
this.setupTopbarButtons(doc);
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
- DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
+ // sthis.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
+ DocUtils.AssignDocField(doc, "globalScriptDatabase", () => Docs.Prototypes.MainScriptDocument(), {});
DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
@@ -1005,10 +943,11 @@ pie title Minerals in my tap water
Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
+ // eslint-disable-next-line no-new
new LinkManager();
- DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
- setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
+ DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
+ setInterval(UPDATE_SERVER_CACHE, 120000);
return doc;
}
static setupFieldInfos(doc:Doc, field="fieldInfos") {
@@ -1025,18 +964,18 @@ pie title Minerals in my tap water
case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
}
- DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+ DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts);
}
});
}
@observable public static ServerVersion: string = ';'
public static async loadCurrentUser() {
- return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
+ return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => {
if (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;
+ runInAction(() => { CurrentUserUtils.ServerVersion = result.version; });
+ ClientUtils.SetCurrentUserEmail(result.email);
resolvedPorts = result.resolvedPorts as any;
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
if (result.cacheDocumentIds)
@@ -1044,13 +983,13 @@ pie title Minerals in my tap water
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
for (let i = 0; i < ids.length; i += batch) {
+ // eslint-disable-next-line no-await-in-loop
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
}
return result;
- } else {
- throw new Error("There should be a user! Why does Dash think there isn't one?");
- }
+ }
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
});
}
@@ -1060,12 +999,12 @@ pie title Minerals in my tap water
linkDatabaseId: string;
}) {
return DocServer.GetRefField(info.userDocumentId).then(async field => {
- Docs.newAccount = !(field instanceof Doc);
+ CurrentUserUtils.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
- const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+ const userDoc = CurrentUserUtils.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") {
+ if (CurrentUserUtils.newAccount) {
+ if (ClientUtils.CurrentUserEmail() === "guest") {
DashboardView.createNewDashboard(undefined, "guest dashboard");
} else {
userDoc.activePage = "home";
@@ -1082,14 +1021,10 @@ pie title Minerals in my tap water
input.type = "file";
input.multiple = true;
input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
- input.onchange = async _e => {
+ input.onchange = async () => {
const file = input.files?.[0];
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 =>
- // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
- // }
const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
@@ -1109,10 +1044,17 @@ pie title Minerals in my tap water
}
}
-ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
-ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
-ScriptingGlobals.add(function getSharingDoc() {return Doc.SharingDoc() }); \ No newline at end of file
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() });
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 82c63695c..08fd80882 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,19 +1,21 @@
+/* eslint-disable no-use-before-define */
import * as interpreter from 'words-to-numbers';
// @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
import type {} from '@types/dom-speech-recognition';
+import { ClientUtils } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor, DocCast } from '../../fields/Types';
+import { Cast, CastCtor } from '../../fields/Types';
import { AudioField, ImageField } from '../../fields/URLField';
-import { Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
import { DictationOverlay } from '../views/DictationOverlay';
import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
import { SelectionManager } from './SelectionManager';
import { UndoManager } from './UndoManager';
+import { DocData } from '../../fields/DocSymbols';
/**
* This namespace provides a singleton instance of a manager that
@@ -64,9 +66,10 @@ export namespace DictationManager {
export let isListening = false;
let isManuallyStopped = false;
- let current: string | undefined = undefined;
+ let current: string | undefined;
let sessionResults: string[] = [];
+ // eslint-disable-next-line new-cap
const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
export type InterimResultHandler = (results: string) => any;
@@ -87,7 +90,7 @@ export namespace DictationManager {
let pendingListen: Promise<string> | string | undefined;
export const listen = async (options?: Partial<ListeningOptions>) => {
- if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
+ if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options));
return innerListen(options);
};
const innerListen = async (options?: Partial<ListeningOptions>) => {
@@ -103,7 +106,7 @@ export namespace DictationManager {
results = await (pendingListen = listenImpl(options));
pendingListen = undefined;
if (results) {
- Utils.CopyText(results);
+ ClientUtils.CopyText(results);
if (overlay) {
DictationOverlay.Instance.isListening = false;
const execute = options?.tryExecute;
@@ -150,29 +153,29 @@ export namespace DictationManager {
recognizer.start();
- return new Promise<string>((resolve, reject) => {
+ return new Promise<string>(resolve => {
recognizer.onerror = (e: any) => {
// e is SpeechRecognitionError but where is that defined?
if (!(indefinite && e.error === 'no-speech')) {
recognizer.stop();
resolve(e);
- //reject(e);
}
};
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
- let matchedTerminator: string | undefined;
- if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
+ const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false));
+ if (options?.terminators && matchedTerminator) {
current = matchedTerminator;
recognizer.abort();
return complete();
}
!isManuallyStopped && handler?.(current);
- //isManuallyStopped && complete();
+ // isManuallyStopped && complete()
+ return undefined;
};
- recognizer.onend = (e: Event) => {
+ recognizer.onend = () => {
if (!indefinite || isManuallyStopped) {
return complete();
}
@@ -182,6 +185,7 @@ export namespace DictationManager {
current = undefined;
}
recognizer.start();
+ return undefined;
};
const complete = () => {
@@ -202,7 +206,7 @@ export namespace DictationManager {
});
};
- export const stop = (salvageSession = true) => {
+ export const stop = (/* salvageSession = true */) => {
if (!isListening || !recognizer) {
return;
}
@@ -212,7 +216,7 @@ export namespace DictationManager {
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
- const results = e.results;
+ const { results } = e;
const transcripts: string[] = [];
for (let i = 0; i < results.length; i++) {
transcripts.push(results.item(i).item(0).transcript.trim());
@@ -233,22 +237,25 @@ export namespace DictationManager {
export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
- export const execute = async (phrase: string) => {
- return UndoManager.RunInBatch(async () => {
+ export const execute = async (phrase: string) =>
+ UndoManager.RunInBatch(async () => {
console.log('PHRASE: ' + phrase);
const targets = SelectionManager.Views;
if (!targets || !targets.length) {
- return;
+ return undefined;
}
+ // eslint-disable-next-line no-param-reassign
phrase = phrase.toLowerCase();
const entry = Independent.get(phrase);
if (entry) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = entry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
+ // eslint-disable-next-line no-await-in-loop
await entry.action(target);
success = true;
}
@@ -256,16 +263,19 @@ export namespace DictationManager {
return success;
}
- for (const entry of Dependent) {
- const regex = entry.expression;
+ // eslint-disable-next-line no-restricted-syntax
+ for (const depEntry of Dependent) {
+ const regex = depEntry.expression;
const matches = regex.exec(phrase);
regex.lastIndex = 0;
if (matches !== null) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = depEntry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
- await entry.action(target, matches);
+ // eslint-disable-next-line no-await-in-loop
+ await depEntry.action(target, matches);
success = true;
}
}
@@ -275,7 +285,6 @@ export namespace DictationManager {
return false;
}, 'Execute Command');
- };
const ConstructorMap = new Map<DocumentType, CastCtor>([
[DocumentType.COL, listSpec(Doc)],
@@ -294,6 +303,7 @@ export namespace DictationManager {
};
const validate = (target: DocumentView, types: DocumentType[]) => {
+ // eslint-disable-next-line no-restricted-syntax
for (const type of types) {
if (tryCast(target, type)) {
return true;
@@ -318,7 +328,9 @@ export namespace DictationManager {
[
'clear',
{
- action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
+ action: (target: DocumentView) => {
+ Doc.GetProto(target.Document).data = new List();
+ },
restrictTo: [DocumentType.COL],
},
],
@@ -328,7 +340,7 @@ export namespace DictationManager {
{
action: (target: DocumentView) => {
const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
- const proto = DocCast(newBox.proto);
+ const proto = newBox[DocData];
const prompt = 'Press alt + r to start dictating here...';
const head = 3;
const anchor = head + prompt.length;
@@ -341,7 +353,7 @@ export namespace DictationManager {
],
]);
- const Dependent = new Array<DependentEntry>(
+ const Dependent = [
{
expression: /create (\w+) documents of type (image|nested collection)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
@@ -361,6 +373,7 @@ export namespace DictationManager {
case 'nested collection':
created = Docs.Create.FreeformDocument([], {});
break;
+ default:
}
created && Doc.AddDocToList(dataDoc, fieldKey, created);
}
@@ -375,7 +388,7 @@ export namespace DictationManager {
mode && (target.Document._type_collection = mode);
},
restrictTo: [DocumentType.COL],
- }
- );
+ },
+ ];
}
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d28c690..ac3721436 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,10 +8,9 @@ import { Cast, DocCast, NumCast, 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';
import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../views/nodes/DocumentView';
import { FocusViewOptions } from '../views/nodes/FieldView';
import { KeyValueBox } from '../views/nodes/KeyValueBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
@@ -20,12 +19,14 @@ import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
export class DocumentManager {
+ // eslint-disable-next-line no-use-before-define
private static _instance: DocumentManager;
public static get Instance(): DocumentManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
- //global holds all of the nodes (regardless of which collection they're in)
+ // global holds all of the nodes (regardless of which collection they're in)
@observable _documentViews = new Set<DocumentView>();
@observable.shallow public CurrentlyLoading: Doc[] = [];
@computed public get DocumentViews() {
@@ -38,7 +39,7 @@ export class DocumentManager {
this._documentViews.delete(dv);
}
- //private constructor so no other class can create a nodemanager
+ // private constructor so no other class can create a nodemanager
private constructor() {
makeObservable(this);
observe(this.CurrentlyLoading, change => {
@@ -52,6 +53,7 @@ export class DocumentManager {
case 'splice':
(change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
break;
+ default:
}
});
}
@@ -101,7 +103,7 @@ export class DocumentManager {
SelectionManager.DeselectView(view);
});
- //gets all views
+ // gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
@@ -135,25 +137,17 @@ export class DocumentManager {
);
}
- public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+ public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
const views: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
- const views = this.getDocumentViews(toFind); //.filter(view => view.Document !== originatingDoc);
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ public getFirstDocumentView = (toFind: Doc): DocumentView | undefined => {
+ if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind);
+ const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== originatingDoc);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getDocumentViews(toFindIn: Doc): DocumentView[] {
- const toFind =
- // Array.from(DocumentManager.Instance.DocumentViews).find(
- // dv =>
- // ((dv.Document.data as any)?.url?.href && (dv.Document.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
- // ((DocCast(dv.Document.annotationOn)?.data as any)?.url?.href && (DocCast(dv.Document.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
- // )?.Document ??
- toFindIn;
-
+ public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
@@ -172,7 +166,7 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
const srcContext = DocCast(doc.annotationOn, DocCast(doc.embedContainer));
- var containerDocContext = srcContext ? [srcContext, doc] : [doc];
+ let containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
DocCast(containerDocContext[0]?.embedContainer) &&
@@ -184,11 +178,13 @@ export class DocumentManager {
return containerDocContext;
}
+ static _howl: Howl;
static playAudioAnno(doc: Doc) {
const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
+ this._howl?.stop();
if (anno instanceof AudioField) {
- new Howl({
+ this._howl = new Howl({
src: [anno.url.href],
format: ['mp3'],
autoplay: true,
@@ -204,19 +200,15 @@ export class DocumentManager {
DocumentManager._overlayViews?.clear();
}
static _overlayViews = new ObservableSet<DocumentView>();
- static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- finished?.();
- };
public static LinkCommonAncestor(linkDoc: Doc) {
- const anchor = (which: number) => {
+ const getAnchor = (which: number) => {
const anch = DocCast(linkDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor);
};
- const anchor1 = anchor(1);
- const anchor2 = anchor(2);
+ const anchor1 = getAnchor(1);
+ const anchor2 = getAnchor(2);
return anchor1
?.docViewPath()
.reverse()
@@ -228,7 +220,7 @@ export class DocumentManager {
// focusing on each context
public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
- let rootContextView = docViewPath.shift();
+ const rootContextView = docViewPath.shift();
await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.Document, options.openLocation);
@@ -242,9 +234,10 @@ export class DocumentManager {
// and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
public showDocument = async (
targetDoc: Doc, // document to display
- options: FocusViewOptions, // options for how to navigate to target
+ optionsIn: FocusViewOptions, // options for how to navigate to target
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ const options = optionsIn;
Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
@@ -253,13 +246,14 @@ export class DocumentManager {
options.toggleTarget = false;
TabDocView.Activate(tabView?._document);
}
- let rootContextView =
+ const 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])!);
+ res(this.getDocumentView(docContextPath[0])!);
+ return;
}
options.didMove = true;
(!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
@@ -270,7 +264,9 @@ export class DocumentManager {
const target = DocCast(targetDoc.annotationOn, targetDoc);
const contextView = this.getDocumentView(DocCast(target.embedContainer));
if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
- await new Promise<void>(waitres => setTimeout(() => waitres()));
+ await new Promise<void>(waitres => {
+ setTimeout(() => waitres());
+ });
}
}
docContextPath.shift();
@@ -281,37 +277,52 @@ export class DocumentManager {
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);
+ if (target) {
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ return;
+ }
+ }
+ finished?.(false);
};
focusViewsInPath = async (
- docView: DocumentView, //
- options: FocusViewOptions,
+ docViewIn: DocumentView, //
+ optionsIn: FocusViewOptions,
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) {
+ let docView = docViewIn;
+ const options = optionsIn;
+ const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+ for (let i = 0; i < maxFocusLength; i++) {
if (docView.Document.layout_fieldKey === 'layout_icon') {
- await new Promise<void>(res => docView.iconify(res));
+ // eslint-disable-next-line no-loop-func
+ const prom = new Promise<void>(res => {
+ docView.iconify(res);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await prom;
options.didMove = true;
}
const nextFocus = docView._props.focus(docView.Document, 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
+ focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
+ // eslint-disable-next-line no-await-in-loop
const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
- contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+ if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+ contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
docView = childDocView;
}
+ return undefined;
};
@action
- restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ restoreDocView(viewSpec: Opt<Doc>, docViewIn: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ const docView = docViewIn;
if (viewSpec && docView) {
- //if (docView.ComponentView instanceof FormattedTextBox)
- //viewSpec !== docView.Document &&
+ // if (docView.ComponentView instanceof FormattedTextBox)
+ // viewSpec !== docView.Document &&
docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
@@ -320,7 +331,7 @@ export class DocumentManager {
if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
if (options.effect) docView.Document[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
DocumentManager._overlayViews.add(contextView);
@@ -332,7 +343,10 @@ export class DocumentManager {
}
}
}
-export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+// eslint-disable-next-line default-param-last
+export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+ let doc = docIn;
+ const options = optionsIn;
const func = () => {
const cv = DocumentManager.Instance.getDocumentView(containingDoc);
const dv = DocumentManager.Instance.getDocumentView(doc, cv);
@@ -343,9 +357,9 @@ export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomC
const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
options.toggleTarget = undefined;
DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- dv && Doc.linkFollowHighlight(dv.Document);
+ const cvFound = DocumentManager.Instance.getDocumentView(containingDoc);
+ const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound);
+ dvFound && Doc.linkFollowHighlight(dvFound.Document);
});
}
};
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9627c5df2..3890b7845 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-mutable-exports */
+/* eslint-disable no-use-before-define */
/**
* The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
*
@@ -13,32 +15,25 @@
*/
import { action, observable, runInAction } from 'mobx';
+import { ClientUtils } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
+import { Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
-import { emptyFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { DocUtils, Docs } from '../documents/Documents';
import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../views/nodes/DocumentView';
+import { dropActionType } from './DropActionTypes';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-import { DocData } from '../../fields/DocSymbols';
-const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-export enum dropActionType {
- embed = 'embed', // create a new embedding of the dragged document for the new location
- copy = 'copy', // copy the dragged document
- move = 'move', // move the dragged document to the drop location after removing it from where it was
- add = 'add', // add the dragged document to the drop location without removing it from where it was
- same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
- inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
- proto = 'proto',
-} // undefined = move, same = move but doesn't call dropPropertiesToRemove
+const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
/**
* Initialize drag
@@ -83,6 +78,9 @@ export namespace DragManager {
let dragLabel: HTMLDivElement;
export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
+ export let AbortDrag: () => void = emptyFunction;
+ export const docsBeingDragged: Doc[] = observable([]);
+ export let DocDragData: DocumentDragData | undefined;
export function Root() {
const root = document.getElementById('root');
@@ -91,7 +89,6 @@ export namespace DragManager {
}
return root;
}
- export let AbortDrag: () => void = emptyFunction;
export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
export type RemoveFunction = (document: Doc | Doc[]) => boolean;
@@ -106,6 +103,7 @@ export namespace DragManager {
// event called when the drag operation results in a drop action
export class DropEvent {
+ // eslint-disable-next-line no-useless-constructor
constructor(
readonly x: number,
readonly y: number,
@@ -115,7 +113,9 @@ export namespace DragManager {
readonly metaKey: boolean,
readonly ctrlKey: boolean,
readonly embedKey: boolean
- ) {}
+ ) {
+ /* empty */
+ }
}
// event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -194,7 +194,7 @@ export namespace DragManager {
userDropAction?: dropActionType;
}
- let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
if (de.complete.docDragData) {
targetAction && (de.complete.docDragData.dropAction = targetAction);
e.stopPropagation();
@@ -228,7 +228,7 @@ export namespace DragManager {
return dropDoc;
};
const finishDrag = async (e: DragCompleteEvent) => {
- const docDragData = e.docDragData;
+ const { docDragData } = e;
setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
@@ -256,7 +256,9 @@ export namespace DragManager {
.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));
+ [...remProps, 'dropPropertiesToRemove'].forEach(prop => {
+ drop[prop] = undefined;
+ });
});
}
return e;
@@ -268,15 +270,18 @@ export namespace DragManager {
}
// drag a button template and drop a new button
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
- params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+ params.forEach(p => {
+ Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc));
+ }); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
+ // eslint-disable-next-line no-param-reassign
options = options ?? {};
options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
@@ -298,7 +303,7 @@ export namespace DragManager {
}
export function snapDragAspect(dragPt: number[], snapAspect: number) {
- let closest = Utils.SNAP_THRESHOLD;
+ let closest = ClientUtils.SNAP_THRESHOLD;
let near = dragPt;
const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
@@ -307,7 +312,7 @@ export namespace DragManager {
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
// let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
- //if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
+ // if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
// Return a object with the x and y coordinates of the intersection
const x = x1 + ua * (x2 - x1);
@@ -315,14 +320,14 @@ export namespace DragManager {
const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
return { pt: [x, y], dist };
};
- SnappingManager.VertSnapLines.forEach((xCoord, i) => {
+ SnappingManager.VertSnapLines.forEach(xCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
near = pt.pt;
}
});
- SnappingManager.HorizSnapLines.forEach((yCoord, i) => {
+ SnappingManager.HorizSnapLines.forEach(yCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
@@ -333,7 +338,7 @@ export namespace DragManager {
}
// snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
- const snapThreshold = Utils.SNAP_THRESHOLD;
+ const snapThreshold = ClientUtils.SNAP_THRESHOLD;
const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
if (snapLines.length) {
const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
@@ -350,14 +355,33 @@ export namespace DragManager {
y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines),
};
}
- export let docsBeingDragged: Doc[] = observable([]);
- export let CanEmbed = false;
- export let DocDragData: DocumentDragData | undefined;
- export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
+
+ 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,
+ complete,
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey,
+ embedKey: SnappingManager.CanEmbed,
+ },
+ };
+ 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);
+ UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
+ options?.dragComplete?.(complete);
+ endDrag?.();
+ }
+ export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return;
DocDragData = dragData as DocumentDragData;
const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
- eles = eles.filter(e => e);
+ const eles = elesIn.filter(e => e);
SnappingManager.SetCanEmbed(dragData.canEmbed || false);
if (!dragDiv) {
dragDiv = document.createElement('div');
@@ -375,9 +399,9 @@ export namespace DragManager {
}
Object.assign(dragDiv.style, { width: '', height: '', overflow: '' });
dragDiv.hidden = false;
- const scalings: number[] = [],
- xs: number[] = [],
- ys: number[] = [];
+ const scalings: number[] = [];
+ const xs: number[] = [];
+ const ys: number[] = [];
const elesCont = {
left: Number.MAX_SAFE_INTEGER,
@@ -385,7 +409,7 @@ export namespace DragManager {
top: Number.MAX_SAFE_INTEGER,
bottom: Number.MIN_SAFE_INTEGER,
};
- let rot: number[] = [];
+ const rot: number[] = [];
const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
const dragElements = eles.map(ele => {
// bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
@@ -471,16 +495,20 @@ export namespace DragManager {
.filter(pb => pb.width && pb.height)
.map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
}
- [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none'));
+ [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))]
+ .map(dele => (dele as any).style)
+ .forEach(style => {
+ style && (style.pointerEvents = 'none');
+ });
dragDiv.appendChild(dragElement);
if (dragElement !== ele) {
- const children = [Array.from(ele.children), Array.from(dragElement.children)];
- while (children[0].length) {
- const childs = [children[0].pop(), children[1].pop()];
+ const dragChildren = [Array.from(ele.children), Array.from(dragElement.children)];
+ while (dragChildren[0].length) {
+ const childs = [dragChildren[0].pop(), dragChildren[1].pop()];
if (childs[0]?.children) {
- children[0].push(...Array.from(childs[0].children));
- children[1].push(...Array.from(childs[1]!.children));
+ dragChildren[0].push(...Array.from(childs[0].children));
+ dragChildren[1].push(...Array.from(childs[1]!.children));
}
if (childs[0]?.scrollTop) childs[1]!.scrollTop = childs[0].scrollTop;
}
@@ -493,7 +521,11 @@ export namespace DragManager {
const hideDragShowOriginalElements = (hide: boolean) => {
dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
+ setTimeout(() =>
+ eles.forEach(ele => {
+ ele.hidden = hide;
+ })
+ );
};
options?.hideSource && hideDragShowOriginalElements(true);
@@ -505,22 +537,7 @@ export namespace DragManager {
const yFromBottom = elesCont.bottom - downY;
let scrollAwaiter: Opt<NodeJS.Timeout>;
- AbortDrag = () => {
- options?.dragComplete?.(new DragCompleteEvent(true, dragData));
- cleanupDrag(true);
- };
-
- const cleanupDrag = action((undo: boolean) => {
- (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
- hideDragShowOriginalElements(false);
- document.removeEventListener('pointermove', moveHandler, true);
- document.removeEventListener('pointerup', upHandler, true);
- SnappingManager.SetIsDragging(false);
- if (batch.end() && undo) UndoManager.Undo();
- docsBeingDragged.length = 0;
- SnappingManager.SetCanEmbed(false);
- });
- var startWindowDragTimer: any;
+ let startWindowDragTimer: any;
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
@@ -580,10 +597,10 @@ export namespace DragManager {
defaultPrevented: true,
eventPhase: e.eventPhase,
isTrusted: true,
- preventDefault: () => ('not implemented for this event' ? false : false),
- isDefaultPrevented: () => ('not implemented for this event' ? false : false),
- stopPropagation: () => ('not implemented for this event' ? false : false),
- isPropagationStopped: () => ('not implemented for this event' ? false : false),
+ preventDefault: () => 'not implemented for this event' && false,
+ isDefaultPrevented: () => 'not implemented for this event' && false,
+ stopPropagation: () => 'not implemented for this event' && false,
+ isPropagationStopped: () => 'not implemented for this event' && false,
persist: emptyFunction,
timeStamp: e.timeStamp,
type: 'dashDragMovePause',
@@ -602,7 +619,9 @@ export namespace DragManager {
const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
lastPt = { x, y };
- dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`));
+ dragElements.forEach((dragElement, i) => {
+ dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`;
+ });
dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
};
const upHandler = (e: PointerEvent) => {
@@ -610,36 +629,32 @@ export namespace DragManager {
startWindowDragTimer = undefined;
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
};
+ const cleanupDrag = action((undo: boolean) => {
+ (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
+ hideDragShowOriginalElements(false);
+ document.removeEventListener('pointermove', moveHandler, true);
+ document.removeEventListener('pointerup', upHandler, true);
+ SnappingManager.SetIsDragging(false);
+ if (batch.end() && undo) UndoManager.Undo();
+ docsBeingDragged.length = 0;
+ SnappingManager.SetCanEmbed(false);
+ });
+ AbortDrag = () => {
+ options?.dragComplete?.(new DragCompleteEvent(true, dragData));
+ cleanupDrag(true);
+ };
document.addEventListener('pointermove', moveHandler, true);
document.addEventListener('pointerup', upHandler, true);
}
-
- async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
- const dropArgs = {
- cancelable: true, // allows preventDefault() to be called to cancel the drop
- bubbles: true,
- detail: {
- ...pos,
- complete,
- shiftKey: e.shiftKey,
- altKey: e.altKey,
- metaKey: e.metaKey,
- ctrlKey: e.ctrlKey,
- embedKey: SnappingManager.CanEmbed,
- },
- };
- 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);
- UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
- options?.dragComplete?.(complete);
- endDrag?.();
- }
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
if (readOnly) {
return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged);
}
- SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
+ SelectionManager.Views.forEach(dv => {
+ dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged;
+ });
+ return undefined;
});
diff --git a/src/client/util/DropActionTypes.ts b/src/client/util/DropActionTypes.ts
new file mode 100644
index 000000000..45b294d97
--- /dev/null
+++ b/src/client/util/DropActionTypes.ts
@@ -0,0 +1,9 @@
+export enum dropActionType {
+ embed = 'embed', // create a new embedding of the dragged document for the new location
+ copy = 'copy', // copy the dragged document
+ move = 'move', // move the dragged document to the drop location after removing it from where it was
+ add = 'add', // add the dragged document to the drop location without removing it from where it was
+ same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+ inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+ proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ed5749d06..0314af06b 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,10 +1,9 @@
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
-import { listSpec } from '../../fields/Schema';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
@@ -13,21 +12,6 @@ import { DragManager } from './DragManager';
import { ScriptingGlobals } from './ScriptingGlobals';
/**
- * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
- * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
- * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
- * "expanded" to create an template instance for the rendered Doc.
- *
- *
- * @param doc the doc to convert to a template
- * @returns 'doc'
- */
-export function MakeTemplate(doc: Doc) {
- doc.isTemplateDoc = makeTemplate(doc, true);
- return doc;
-}
-
-/**
*
* Recursively converts 'doc' into a template that can be used to render other documents.
*
@@ -63,26 +47,22 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
}
return isTemplate;
}
-export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
- data?.draggedDocuments.map((doc, i) => {
- 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.name)) {
- 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.dropPropertiesToRemove, listSpec('string'), []);
- const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
- remProps.map(prop => (dbox[prop] = undefined));
- }
- } else if (!doc.onDragStart && !doc.isButtonBar) {
- dbox = makeUserTemplateButton(doc);
- } else if (doc.isButtonBar) {
- dbox.ignoreClick = true;
- }
- data.droppedDocuments[i] = dbox;
- });
+
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+ doc.isTemplateDoc = makeTemplate(doc, true);
+ return doc;
}
+
export function makeUserTemplateButton(doc: Doc) {
const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
if (layoutDoc.type !== DocumentType.FONTICON) {
@@ -106,7 +86,30 @@ export function makeUserTemplateButton(doc: Doc) {
dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
return dbox;
}
+export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
+ data?.draggedDocuments.forEach((doc, i) => {
+ 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.name)) {
+ 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 = StrListCast(dbox.dropPropertiesToRemove);
+ const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+ remProps.forEach(prop => {
+ dbox[prop] = undefined;
+ });
+ }
+ } else if (!doc.onDragStart && !doc.isButtonBar) {
+ dbox = makeUserTemplateButton(doc);
+ } else if (doc.isButtonBar) {
+ dbox.ignoreClick = true;
+ }
+ data.droppedDocuments[i] = dbox;
+ });
+}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function convertToButtons(dragData: any) {
convertDropDataToButtons(dragData as DragManager.DocumentDragData);
},
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index f4f879208..8d84dbad8 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -5,19 +7,20 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, StrCast } from '../../fields/Types';
-import { Utils } from '../../Utils';
import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './GroupManager.scss';
import { GroupMemberView } from './GroupMemberView';
import { SettingsManager } from './SettingsManager';
import { SharingManager, User } from './SharingManager';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
/**
* Interface for options for the react-select component
@@ -29,6 +32,7 @@ export interface UserOptions {
@observer
export class GroupManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@observable private users: string[] = []; // list of users populated from the database.
@@ -55,7 +59,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
populateUsers = async () => {
if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
}
@@ -135,7 +139,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
hasEditAccess(groupDoc: Doc): boolean {
if (!groupDoc) return false;
const accessList: string[] = JSON.parse(StrCast(groupDoc.owners));
- return accessList.includes(Doc.CurrentUserEmail) || this.adminGroupMembers?.includes(Doc.CurrentUserEmail);
+ return accessList.includes(ClientUtils.CurrentUserEmail()) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail());
}
/**
@@ -147,7 +151,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
const groupDoc = new Doc('GROUP:' + name, true);
groupDoc.title = name;
- groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
+ groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail()]);
groupDoc.members = JSON.stringify(memberEmails);
this.addGroup(groupDoc);
}
@@ -159,7 +163,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
return true;
}
return false;
@@ -176,11 +180,11 @@ export class GroupManager extends ObservableReactComponent<{}> {
Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
SharingManager.Instance.removeGroup(group);
const members = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail)) {
+ if (members.includes(ClientUtils.CurrentUserEmail())) {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -201,7 +205,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
@@ -215,10 +219,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
const memberList = JSON.parse(StrCast(groupDoc.members));
const index = memberList.indexOf(email);
if (index !== -1) {
- const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
}
@@ -260,7 +263,10 @@ export class GroupManager extends ObservableReactComponent<{}> {
alert('Please select a unique group name');
return;
}
- this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+ this.createGroupDoc(
+ value,
+ this.selectedUsers?.map(user => user.value)
+ );
this.selectedUsers = null;
this.inputRef.current!.value = '';
this.buttonColour = '#979797';
@@ -271,7 +277,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
TaskCompletionBox.textDisplayed = 'Group created!';
TaskCompletionBox.taskCompleted = true;
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2000
);
};
@@ -288,7 +296,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
</p>
<div className="close-button">
<Button
- icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+ icon={<FontAwesomeIcon icon="times" size="lg" />}
onClick={action(() => {
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
@@ -298,7 +306,17 @@ export class GroupManager extends ObservableReactComponent<{}> {
</div>
</div>
<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'))} />
+ <input
+ ref={this.inputRef}
+ onKeyDown={this.handleKeyDown}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ 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
@@ -306,7 +324,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
isMulti
options={this.options}
onChange={this.handleChange}
- placeholder={'Select users'}
+ placeholder="Select users"
value={this.selectedUsers}
closeMenuOnSelect={false}
styles={{
@@ -331,8 +349,8 @@ export class GroupManager extends ObservableReactComponent<{}> {
}}
/>
</div>
- <div className={'create-button'}>
- <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ <div className="create-button">
+ <Button text="Create" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
</div>
</div>
);
@@ -340,7 +358,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
return (
<MainViewModal
isDisplayed={this.createGroupModalOpen}
- interactive={true}
+ interactive
contents={contents}
dialogueBoxStyle={{ width: '90%', height: '70%' }}
closeOnExternalClick={action(() => {
@@ -368,28 +386,59 @@ export class GroupManager extends ObservableReactComponent<{}> {
return (
<div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
{this.groupCreationModal}
- {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
+ {this.currentGroup ? (
+ <GroupMemberView
+ group={this.currentGroup}
+ onCloseButtonClick={action(() => {
+ this.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div className="group-heading">
<p>
<b>Manage Groups</b>
</p>
- <Button 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)} />
+ <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'))}>
+ <div
+ className="sort-groups"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
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={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <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))}>
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} />
+ <div
+ className="group-info"
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}>
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}
+ />
</div>
</div>
))}
@@ -400,6 +449,6 @@ export class GroupManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 894583711..c703c3f98 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,4 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -8,7 +11,6 @@ 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 {
@@ -38,20 +40,23 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
className="group-title"
style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
value={StrCast(this.group.title || this.group.groupName)}
- onChange={e => (this.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)} />
+ onChange={e => {
+ this.group.title = e.currentTarget.value;
+ }}
+ disabled={!hasEditAccess}
+ />
+ <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.group) ? (
<div className="group-buttons">
<div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
className="add-member-dropdown"
- isSearchable={true}
+ isSearchable
options={options}
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
- placeholder={'Add members'}
+ placeholder="Add members"
value={null}
styles={{
control: () => ({
@@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
</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'))}>
+ <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>
@@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
<div className="editing-row" key={member}>
<div className="user-email">{member}</div>
{hasEditAccess ? (
- <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
- <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
+ <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+ <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
</div>
) : null}
</div>
@@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
}
render() {
- return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
+ return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
}
}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 2f1a336cc..52d0223d5 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,6 +1,12 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable no-empty */
+/* eslint-disable no-continue */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-param-reassign */
import * as qs from 'query-string';
import { Doc } from '../../fields/Doc';
-import { OmitKeys, Utils } from '../../Utils';
+import { OmitKeys, ClientUtils } from '../../ClientUtils';
import { DocServer } from '../DocServer';
import { DashboardView } from '../views/DashboardView';
@@ -32,6 +38,7 @@ export namespace HistoryUtil {
case 'doc':
onDocUrl(url);
break;
+ default:
}
}
}
@@ -124,11 +131,11 @@ export namespace HistoryUtil {
const val = customParser(pathname, opts, current);
if (val === null) {
return undefined;
- } else if (val === undefined) {
+ }
+ if (val === undefined) {
return current;
- } else {
- return val;
}
+ return val;
}
return current;
};
@@ -136,32 +143,33 @@ export namespace HistoryUtil {
function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
stringifiers[type] = state => {
- let path = Utils.prepend(`/${type}`);
+ let path = ClientUtils.prepend(`/${type}`);
if (customStringifier) {
path = customStringifier(state, path);
}
const queryObj = OmitKeys(state, keys).extract;
const query: any = {};
- Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
+ Object.keys(queryObj).forEach(key => {
+ query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+ });
const queryString = qs.stringify(query);
return path + (queryString ? `?${queryString}` : '');
};
}
addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
- if (pathname.length !== 2) return undefined;
-
- current.initializers = current.initializers || {};
- const docId = pathname[1];
- current.docId = docId;
- });
- addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
- return `${current}/${state.docId}`;
+ if (pathname.length === 2) {
+ current.initializers = current.initializers || {};
+ const docId = pathname[1];
+ current.docId = docId;
+ }
+ return undefined;
});
+ addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`);
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
- const search = location.search;
+ const { search } = location;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
const pathnameSplit = pathname.split('/');
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index c5f307f44..6dc96f1b5 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,14 +1,12 @@
import { action, runInAction } from 'mobx';
-import { simulateMouseClick } from '../../Utils';
+import { simulateMouseClick } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { Cast, StrCast } from '../../fields/Types';
import { WebField } from '../../fields/URLField';
-import { DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
import { DocumentView } from '../views/nodes/DocumentView';
import { DocumentManager } from './DocumentManager';
-import { SearchUtil } from './SearchUtil';
import { SelectionManager } from './SelectionManager';
export namespace Hypothesis {
@@ -33,7 +31,7 @@ export namespace Hypothesis {
// await SearchUtil.Search('web', true).then(
// action(async (res: SearchUtil.DocSearchResult) => {
// const docs = res.docs;
- // const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+ // const filteredDocs = docs.filter(doc => doc.author === ClientUtils.CurrentUserEmail() && doc.type === DocumentType.WEB && doc.data);
// filteredDocs.forEach(doc => {
// uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
// });
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index d99828956..8d4eefa7e 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,10 +1,10 @@
+import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { Cast, StrCast, NumCast } from '../../../fields/Types';
import { Networking } from '../../Network';
-import { Id } from '../../../fields/FieldSymbols';
-import { Utils } from '../../../Utils';
-import { DocData } from '../../../fields/DocSymbols';
export namespace ImageUtils {
export type imgInfo = {
@@ -16,7 +16,7 @@ export namespace ImageUtils {
};
export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
const field = Cast(document.data, ImageField);
- return field ? await Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+ return field ? Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
};
export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
@@ -35,7 +35,7 @@ export namespace ImageUtils {
export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
const a = document.createElement('a');
- a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
+ a.href = ClientUtils.prepend(`/imageHierarchyExport/${collection[Id]}`);
a.download = `Dash Export [${StrCast(collection.title)}].zip`;
a.click();
};
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 58a09b9c9..db1e3d6cd 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -1,10 +1,13 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { EditableView } from '../../views/EditableView';
-import { action, computed } from 'mobx';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc } from '../../../fields/Doc';
-import { StrCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, StrCast } from '../../../fields/Types';
+import { EditableView } from '../../views/EditableView';
interface KeyValueProps {
Document: Doc;
@@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
alignItems: 'center',
alignContent: 'center',
}}>
- <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} />
- <div className={'key_container'} style={keyValueStyle}>
- <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} />
+ <input
+ onChange={e => {
+ this.onDataDoc = e.target.checked;
+ }}
+ ref={this.checkRef}
+ style={{ margin: '0 10px 0 15px' }}
+ type="checkbox"
+ title="Add to Data Document?"
+ checked={this.onDataDoc}
+ />
+ <div className="key_container" style={keyValueStyle}>
+ <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine />
</div>
- <div className={'value_container'} style={keyValueStyle}>
- <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} />
+ <div className="value_container" style={keyValueStyle}>
+ <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine />
</div>
- <div onClick={() => this.props.remove(this)} title={'Delete Entry'}>
+ <div onClick={() => this.props.remove(this)} title="Delete Entry">
<FontAwesomeIcon
- icon={'plus'}
- color={'red'}
- size={'1x'}
+ icon="plus"
+ color="red"
+ size="1x"
style={{
marginLeft: 15,
marginRight: 15,
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index a2f5826fe..a07550e09 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Utils } from '../../Utils';
+import { Gestures } from '../../pen-gestures/GestureTypes';
import './InteractionUtils.scss';
export namespace InteractionUtils {
@@ -9,81 +9,86 @@ export namespace InteractionUtils {
export const PENTYPE = 'pen';
export const ERASERTYPE = 'eraser';
- const POINTER_PEN_BUTTON = -1;
- const REACT_POINTER_PEN_BUTTON = 0;
const ERASER_BUTTON = 5;
- export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> {
- constructor(
- readonly fingers: number,
- readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent
- ) {}
- }
-
- export interface MultiTouchEventDisposer {
- (): void;
- }
-
- /**
- *
- * @param element - element to turn into a touch target
- * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart)
- */
- export function MakeMultiTouchTarget(element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- element.addEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler);
- // }
- return () => {
- element.removeEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler);
- // }
- };
- }
-
- /**
- * Turns an element onto a target for touch hold handling.
- * @param element - element to add events to
- * @param func - function to add to the event
- */
- export function MakeHoldTouchTarget(element: HTMLElement, func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const handler = (e: Event) => func(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- element.addEventListener('dashOnTouchHoldStart', handler);
- return () => {
- element.removeEventListener('dashOnTouchHoldStart', handler);
- };
- }
-
- export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
- const myTouches = new Array<React.Touch>();
- for (const pt of mte.touches) {
- if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) {
- for (const tPt of mte.targetTouches) {
- if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
- if (pt && prevPoints.has(pt.identifier)) {
- myTouches.push(pt);
- }
- }
+ export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
+ // if arrow/line/circle, the two end points should be the starting and the ending point
+ let left = points[0].X;
+ let top = points[0].Y;
+ let right = points[1].X;
+ let bottom = points[1].Y;
+ if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
+ // pointer is up (first and last points are the same)
+ if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape as any as Gestures)) {
+ // otherwise take max and min
+ const xs = points.map(p => p.X);
+ const ys = points.map(p => p.Y);
+ right = Math.max(...xs);
+ left = Math.min(...xs);
+ bottom = Math.max(...ys);
+ top = Math.min(...ys);
+ }
+ } else {
+ // if in the middle of drawing
+ // take first and last points
+ right = points[points.length - 1].X;
+ left = points[0].X;
+ bottom = points[points.length - 1].Y;
+ top = points[0].Y;
+ if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
+ // switch left/right and top/bottom if needed
+ if (left > right) {
+ const temp = right;
+ right = left;
+ left = temp;
+ }
+ if (top > bottom) {
+ const temp = top;
+ top = bottom;
+ bottom = temp;
}
}
}
- // if (mte.touches.length !== myTouches.length) {
- // throw Error("opo")
- // }
- return myTouches;
+ const polyPts = [];
+ switch (shape) {
+ case Gestures.Rectangle:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: left, Y: top });
+ break;
+ case Gestures.Triangle:
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: (right + left) / 2, Y: top });
+ polyPts.push({ X: left, Y: bottom });
+ break;
+ case Gestures.Circle:
+ {
+ const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+ const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+ const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+ for (let x = centerX - radius; x < centerX + radius; x++) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ polyPts.push({ X: x, Y: y });
+ }
+ for (let x = centerX + radius; x > centerX - radius; x--) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ const newY = centerY - (y - centerY);
+ polyPts.push({ X: x, Y: newY });
+ }
+ polyPts.push({ X: centerX - radius, Y: Math.sqrt(radius ** 2 - (-radius) ** 2) + centerY });
+ }
+ break;
+
+ case Gestures.Line:
+ default:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ break;
+ }
+ return polyPts;
}
export function CreatePolyline(
@@ -101,20 +106,20 @@ export namespace InteractionUtils {
arrowEnd: string,
markerScale: number,
dash: string | undefined,
- scalex: number,
- scaley: number,
+ scalexIn: number,
+ scaleyIn: number,
shape: string,
pevents: string,
opacity: number,
nodefs: boolean,
downHdlr?: (e: React.PointerEvent) => void,
- mask?: boolean,
- dropshadow?: string
+ mask?: boolean
+ // dropshadow?: string
) {
const pts = shape ? makePolygon(shape, points) : points;
- if (isNaN(scalex)) scalex = 1;
- if (isNaN(scaley)) scaley = 1;
+ const scalex = isNaN(scalexIn) ? 1 : scalexIn;
+ const scaley = isNaN(scaleyIn) ? 1 : scaleyIn;
const toScr = (p: { X: number; Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
const strpts = bezier
@@ -137,7 +142,7 @@ export namespace InteractionUtils {
<defs>
{!mask ? null : (
<filter id={`mask${defGuid}`} x="-1" y="-1" width="500%" height="500%">
- <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5"></feGaussianBlur>
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5" />
</filter>
)}
{arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : (
@@ -172,7 +177,7 @@ export namespace InteractionUtils {
<Tag
d={bezier ? strpts + (arrowStart || arrowEnd ? ' ' : '') : undefined}
points={bezier ? undefined : strpts}
- //filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
+ // filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
@@ -193,83 +198,6 @@ export namespace InteractionUtils {
);
}
- export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
- if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
- //pointer is up (first and last points are the same)
- if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) {
- //if arrow or line, the two end points should be the starting and the ending point
- var left = points[0].X;
- var top = points[0].Y;
- var right = points[1].X;
- var bottom = points[1].Y;
- } else {
- //otherwise take max and min
- const xs = points.map(p => p.X);
- const ys = points.map(p => p.Y);
- right = Math.max(...xs);
- left = Math.min(...xs);
- bottom = Math.max(...ys);
- top = Math.min(...ys);
- }
- } else {
- //if in the middle of drawing
- //take first and last points
- right = points[points.length - 1].X;
- left = points[0].X;
- bottom = points[points.length - 1].Y;
- top = points[0].Y;
- if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
- //switch left/right and top/bottom if needed
- if (left > right) {
- const temp = right;
- right = left;
- left = temp;
- }
- if (top > bottom) {
- const temp = top;
- top = bottom;
- bottom = temp;
- }
- }
- }
- points = [];
- switch (shape) {
- case GestureUtils.Gestures.Rectangle:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: top });
- points.push({ X: right, Y: bottom });
- points.push({ X: left, Y: bottom });
- points.push({ X: left, Y: top });
- break;
- case GestureUtils.Gestures.Triangle:
- points.push({ X: left, Y: bottom });
- points.push({ X: right, Y: bottom });
- points.push({ X: (right + left) / 2, Y: top });
- points.push({ X: left, Y: bottom });
- break;
- case GestureUtils.Gestures.Circle:
- const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
- const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
- const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
- for (var x = centerX - radius; x < centerX + radius; x++) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- points.push({ X: x, Y: y });
- }
- for (var x = centerX + radius; x > centerX - radius; x--) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- const newY = centerY - (y - centerY);
- points.push({ X: x, Y: newY });
- }
- points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY });
- break;
-
- case GestureUtils.Gestures.Line:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: bottom });
- break;
- }
- return points;
- }
/**
* Returns whether or not the pointer event passed in is of the type passed in
* @param e - pointer event. this event could be from a mouse, a pen, or a finger
@@ -279,10 +207,11 @@ export namespace InteractionUtils {
// prettier-ignore
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
- case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+ case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
- case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
- }
+ case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
+ default:
+ } // prettier-ignore
return e.pointerType === type;
}
@@ -292,7 +221,7 @@ export namespace InteractionUtils {
* @param pt2
*/
export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
- return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+ return Math.sqrt((pt1.clientX - pt2.clientX) ** 2 + (pt1.clientY - pt2.clientY) ** 2);
}
/**
@@ -349,72 +278,4 @@ export namespace InteractionUtils {
}
return 0;
}
-
- export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
- for (const touch of newTouches) {
- if (touch) {
- const oldTouch = oldTouches.get(touch.identifier);
- if (oldTouch) {
- if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- // These might not be very useful anymore, but I'll leave them here for now -syip2
- {
- /**
- * Returns the type of Touch Interaction from a list of points.
- * Also returns any data that is associated with a Touch Interaction
- * @param pts - List of points
- */
- // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
- // const leniency = 200;
- // switch (pts.length) {
- // case 1:
- // return { type: OneFinger };
- // case 2:
- // return { type: TwoSeperateFingers };
- // case 3:
- // let pt1 = pts[0];
- // let pt2 = pts[1];
- // let pt3 = pts[2];
- // if (pt1 && pt2 && pt3) {
- // let dist12 = TwoPointEuclidist(pt1, pt2);
- // let dist23 = TwoPointEuclidist(pt2, pt3);
- // let dist13 = TwoPointEuclidist(pt1, pt3);
- // let dist12close = dist12 < leniency;
- // let dist23close = dist23 < leniency;
- // let dist13close = dist13 < leniency;
- // let xor2313 = dist23close ? !dist13close : dist13close;
- // let xor = dist12close ? !xor2313 : xor2313;
- // // three input xor because javascript doesn't have logical xor's
- // if (xor) {
- // let points: number[] = [];
- // let min = Math.min(dist12, dist23, dist13);
- // switch (min) {
- // case dist12:
- // points = [0, 1, 2];
- // break;
- // case dist23:
- // points = [1, 2, 0];
- // break;
- // case dist13:
- // points = [0, 2, 1];
- // break;
- // }
- // return { type: TwoToOneFingers, data: points };
- // }
- // else {
- // return { type: ThreeSeperateFingers, data: null };
- // }
- // }
- // default:
- // return { type: undefined };
- // }
- // }
- }
}
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
index de2457a5a..fb21f78ee 100644
--- a/src/client/util/KeyCodes.ts
+++ b/src/client/util/KeyCodes.ts
@@ -133,4 +133,4 @@ export class KeyCodes {
public static NUM_9: number = 57;
public static SUBTRACT: number = 189;
public static ADD: number = 187;
-} \ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 6c0bf3242..09262f5ac 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,5 +1,5 @@
import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, FieldType, FieldResult, Opt } from '../../fields/Doc';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
@@ -44,7 +44,7 @@ export class LinkFollower {
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
- const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
+ const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
@@ -53,7 +53,7 @@ export class LinkFollower {
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
- var count = 0;
+ let count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
if (!followLinks.length) {
finished?.();
@@ -69,7 +69,7 @@ export class LinkFollower {
? linkDoc.link_anchor_2
: linkDoc.link_anchor_1
) as Doc;
- const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+ const srcAnchor: Doc = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
if (target) {
const doFollow = (canToggle?: boolean) => {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
@@ -113,10 +113,12 @@ export class LinkFollower {
}
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ // eslint-disable-next-line prefer-destructuring
target.x = moveTo[0];
movedTarget = true;
}
if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ // eslint-disable-next-line prefer-destructuring
target.y = moveTo[1];
movedTarget = true;
}
@@ -130,6 +132,7 @@ export class LinkFollower {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
SelectionManager.DeselectAll();
return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
@@ -138,6 +141,6 @@ ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
export function FollowLinkScript() {
return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
}
-export function IsFollowLinkScript(field: FieldResult<Field>) {
+export function IsFollowLinkScript(field: FieldResult<FieldType>) {
return ScriptCast(field)?.script.originalScript.includes('return followLink(');
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8972bf705..c986bd674 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,12 +1,16 @@
-import { action, makeObservable, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import * as rp from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from '../../fields/Doc';
import { DirectLinks, DocData } from '../../fields/DocSymbols';
import { FieldLoader } from '../../fields/FieldLoader';
+import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
+import { DocumentType } from '../documents/DocumentTypes';
import { ScriptingGlobals } from './ScriptingGlobals';
/*
* link doc:
@@ -21,6 +25,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
* - user defined kvps
*/
export class LinkManager {
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: LinkManager;
@observable.shallow userLinkDBs: Doc[] = [];
@observable public currentLink: Opt<Doc> = undefined;
@@ -45,6 +50,7 @@ export class LinkManager {
constructor() {
makeObservable(this);
LinkManager._instance = this;
+ Doc.AddLink = this.addLink;
this.createlink_relationshipLists();
// 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
@@ -56,7 +62,7 @@ export class LinkManager {
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(
link &&
- action(lAnchProtoProtos => {
+ action(() => {
Doc.AddDocToList(Doc.UserDoc(), 'links', link);
lAnchs[0]?.[DocData][DirectLinks].add(link);
lAnchs[1]?.[DocData][DirectLinks].add(link);
@@ -73,7 +79,7 @@ export class LinkManager {
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 => {
+ action(() => {
link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
})
@@ -84,7 +90,7 @@ export class LinkManager {
);
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- 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
+ const toRealField = (field: FieldType) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
if (userLinkDBDoc.data) {
observe(
userLinkDBDoc.data,
@@ -95,7 +101,8 @@ export class LinkManager {
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
(change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -115,6 +122,8 @@ export class LinkManager {
added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
});
+ break;
+ default:
}
},
true
@@ -128,7 +137,8 @@ export class LinkManager {
case 'splice':
(change as any).added.forEach(watchUserLinkDB);
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -138,7 +148,7 @@ export class LinkManager {
}
public createlink_relationshipLists = () => {
- //create new lists for link relations and their associated colors if the lists don't already exist
+ // create new lists for link relations and their associated colors if the lists don't already exist
!Doc.UserDoc().link_relationshipList && (Doc.UserDoc().link_relationshipList = new List<string>());
!Doc.UserDoc().link_ColorList && (Doc.UserDoc().link_ColorList = new List<string>());
!Doc.UserDoc().link_relationshipSizes && (Doc.UserDoc().link_relationshipSizes = new List<number>());
@@ -148,7 +158,8 @@ export class LinkManager {
Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
- setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
+ // eslint-disable-next-line no-use-before-define
+ setTimeout(UPDATE_SERVER_CACHE, 100);
}
}
public deleteLink(linkDoc: Doc) {
@@ -198,7 +209,7 @@ export class LinkManager {
this.relatedLinker(anchor).forEach(link => {
if (link.link_relationship && link.link_relationship !== '-ungrouped-') {
const relation = StrCast(link.link_relationship);
- const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
+ const anchorRelation: string = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
const group = anchorGroups.get(anchorRelation);
anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
} else {
@@ -229,18 +240,48 @@ export class LinkManager {
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
-
- // const a1 = DocCast(linkDoc.link_anchor_1);
- // const a2 = DocCast(linkDoc.link_anchor_2);
- // if (linkDoc.link_matchEmbeddings) {
- // return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
- // }
- // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
- // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
+ return undefined;
}
}
+let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
+export function UPDATE_SERVER_CACHE() {
+ const prototypes = Object.values(DocumentType)
+ .filter(type => type !== DocumentType.NONE)
+ .map(type => DocServer._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);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+ }
+ });
+ LinkManager.Instance.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
+ Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
+ const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+ const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+ Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+
+ rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
+ body: {
+ cacheDocumentIds,
+ },
+ json: true,
+ });
+}
+
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function links(doc: any) {
return new List(LinkManager.Links(doc));
},
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7638e2ce0..e5e69c5ac 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,8 +1,10 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { Networking } from '../Network';
import { CurrentUserUtils } from './CurrentUserUtils';
+
export class PingManager {
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: PingManager;
@observable IsBeating = true;
static get Instance(): PingManager {
@@ -29,7 +31,9 @@ export class PingManager {
sendPing = async (): Promise<void> => {
try {
const res = await Networking.PostToServer('/ping', { date: new Date() });
- runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+ runInAction(() => {
+ CurrentUserUtils.ServerVersion = res.message;
+ });
!this.IsBeating && this.setIsBeating(true);
} catch {
if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 35b1579df..05fb849fd 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -6,23 +6,16 @@ import { SettingsManager } from './SettingsManager';
@observer
export class RTFMarkup extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: RTFMarkup;
@observable private isOpen = false; // whether the SharingManager modal is open or not
- @action
- public open = () => (this.isOpen = true);
-
- @action
- public close = () => (this.isOpen = false);
-
constructor(props: {}) {
super(props);
makeObservable(this);
RTFMarkup.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@@ -30,11 +23,11 @@ export class RTFMarkup extends React.Component<{}> {
return (
<div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
- <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
+ <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+ <b style={{ fontSize: 'larger' }}>(( any text ))</b>
{` submit text to Chat GPT to have results appended afterward`}
</p>
<p>
@@ -129,13 +122,23 @@ export class RTFMarkup extends React.Component<{}> {
);
}
+ @action
+ public open = () => {
+ this.isOpen = true;
+ };
+
+ @action
+ public close = () => {
+ this.isOpen = false;
+ };
+
render() {
return (
<MainViewModal
dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
contents={this.cheatSheet}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
/>
);
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index b881f18b4..2c8fdf483 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -14,6 +14,7 @@ export class ReplayMovements {
private isPlaying: boolean;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: ReplayMovements;
static get Instance(): ReplayMovements {
return ReplayMovements._instance;
@@ -90,7 +91,7 @@ export class ReplayMovements {
loadPresentation = (presentation: Presentation) => {
const { movements } = presentation;
if (movements === null) {
- throw '[recordingApi.ts] followMovements() failed: no presentation data';
+ throw new Error('[recordingApi.ts] followMovements() failed: no presentation data');
}
movements.forEach((movement, i) => {
@@ -106,9 +107,7 @@ export class ReplayMovements {
// returns undefined if the docView isn't open on the screen
getCollectionFFView = (doc: Doc) => {
const isInView = DocumentManager.Instance.getDocumentView(doc);
- if (isInView) {
- return isInView.ComponentView as CollectionFreeFormView;
- }
+ return isInView?.ComponentView as CollectionFreeFormView;
};
// will open the doc in a tab then return the CollectionFFView that holds it
@@ -136,9 +135,9 @@ export class ReplayMovements {
if (movements === null) return new Map();
// generate a set of all unique docIds
const docIdtoFirstMove = new Map<Doc, Movement>();
- for (const move of movements) {
+ movements.forEach(move => {
if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
- }
+ });
return docIdtoFirstMove;
};
@@ -151,10 +150,10 @@ export class ReplayMovements {
// console.info('playMovements', presentation, timeViewed, docIdtoDoc);
if (presentation.movements === null || presentation.movements.length === 0) {
- //|| this.playFFView === null) {
- return new Error('[recordingApi.ts] followMovements() failed: no presentation data');
+ // || this.playFFView === null) {
+ return '[recordingApi.ts] followMovements() failed: no presentation data';
}
- if (this.isPlaying) return;
+ if (this.isPlaying) return undefined;
this.isPlaying = true;
Doc.UserDoc().presentationMode = 'watching';
@@ -170,10 +169,10 @@ export class ReplayMovements {
// for the open tabs, set it to the first move
const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
- for (const [doc, firstMove] of docIdtoFirstMove) {
+ Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => {
const colFFView = this.getCollectionFFView(doc);
if (colFFView) this.zoomAndPan(firstMove, colFFView);
- }
+ });
};
handleFirstMovements();
@@ -197,5 +196,6 @@ export class ReplayMovements {
}
}, timeDiff);
});
+ return undefined;
};
}
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 87509f2ea..9158f6c0b 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals';
export class ScriptManager {
static _initialized = false;
+ // eslint-disable-next-line no-use-before-define
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
private constructor() {
@@ -58,14 +60,16 @@ export class ScriptManager {
const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
const paramNames = params.reduce((o: string, p: string) => {
+ let out = o;
if (params.indexOf(p) === params.length - 1) {
- o = o + p.split(':')[0].trim();
+ out += p.split(':')[0].trim();
} else {
- o = o + p.split(':')[0].trim() + ',';
+ out += p.split(':')[0].trim() + ',';
}
- return o;
+ return out;
}, '' as string);
+ // eslint-disable-next-line no-new-func
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 422e708bc..f7d7ba6a4 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -4,12 +4,14 @@
// // @ts-ignore
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d';
+// eslint-disable-next-line node/no-unpublished-import
import * as ts from 'typescript';
-import { Doc, Field } from '../../fields/Doc';
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
+import { Doc, FieldType } from '../../fields/Doc';
import { RefField } from '../../fields/RefField';
import { ScriptField } from '../../fields/ScriptField';
-import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
+import { ScriptingGlobals, scriptingGlobals } from './ScriptingGlobals';
+
export { ts };
export interface ScriptSuccess {
@@ -30,6 +32,7 @@ export type ScriptParam = { [name: string]: string };
export interface CompiledScript {
readonly compiled: true;
readonly originalScript: string;
+ // eslint-disable-next-line no-use-before-define
readonly options: Readonly<ScriptOptions>;
run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
}
@@ -47,6 +50,7 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
return false;
}
+// eslint-disable-next-line no-use-before-define
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors.length) || !script) {
@@ -60,6 +64,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
// let params: any[] = [Docs, ...fieldTypes];
const compiledFunction = (() => {
try {
+ // eslint-disable-next-line no-new-func
return new Function(...paramNames, `return ${script}`);
} catch (e) {
console.log(e);
@@ -68,20 +73,17 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
})();
if (!compiledFunction) return { compiled: false, errors };
const { capturedVariables = {} } = options;
+ // eslint-disable-next-line default-param-last
const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
const argsArray: any[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const name of customParams) {
- if (name === 'this') {
- continue;
- }
- if (name in args) {
- argsArray.push(args[name]);
- } else {
- argsArray.push(capturedVariables[name]);
+ if (name !== 'this') {
+ argsArray.push(name in args ? args[name] : capturedVariables[name]);
}
}
const thisParam = args.this || capturedVariables.this;
- let batch: { end(): void } | undefined = undefined;
+ let batch: { end(): void } | undefined;
try {
if (!options.editable) {
batch = Doc.MakeReadOnly();
@@ -109,7 +111,7 @@ class ScriptingCompilerHost {
files: File[] = [];
// getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
- getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+ getSourceFile(fileName: string, languageVersion: any /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): any | undefined {
const contents = this.readFile(fileName);
if (contents !== undefined) {
return ts.createSourceFile(fileName, contents, languageVersion, true);
@@ -118,11 +120,11 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
- getDefaultLibFileName(options: any): string {
+ getDefaultLibFileName(/* options: any */): string {
return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
file.content = content;
} else {
@@ -145,7 +147,7 @@ class ScriptingCompilerHost {
return this.files.some(file => file.fileName === fileName);
}
readFile(fileName: string): string | undefined {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
return file.content;
}
@@ -157,7 +159,7 @@ export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
export type Transformer = {
transformer: ts.TransformerFactory<ts.Node>;
- getVars?: () => { [name: string]: Field };
+ getVars?: () => { [name: string]: FieldType };
};
export interface ScriptOptions {
requiredType?: string; // does function required a typed return value
@@ -210,12 +212,13 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const newCaptures = options.transformer.getVars?.();
if (Object.keys(newCaptures ?? {}).length) {
// tslint:disable-next-line: prefer-object-spread
- //options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
+ // options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
- const transformed = result.transformed;
+ const { transformed } = result;
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
});
+ // eslint-disable-next-line no-param-reassign
script = printer.printFile(transformed[0].getSourceFile());
}
result.dispose();
@@ -224,19 +227,23 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if ('this' in params || 'this' in capturedVariables) {
paramNames.push('this');
}
+ // eslint-disable-next-line no-restricted-syntax
for (const key in params) {
- if (key === 'this') continue;
- paramNames.push(key);
+ if (key !== 'this') {
+ paramNames.push(key);
+ }
}
const paramList = paramNames.map(key => {
const val = params[key];
return `${key}: ${val}`;
});
+ // eslint-disable-next-line no-restricted-syntax
for (const key in capturedVariables) {
- if (key === 'this') continue;
- const val = capturedVariables[key];
- paramNames.push(key);
- paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ if (key !== 'this') {
+ const val = capturedVariables[key];
+ paramNames.push(key);
+ paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ }
}
const paramString = paramList.join(', ');
const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
@@ -261,6 +268,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
}
ScriptingGlobals.add(CompileScript);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
return script?.script.run({ this: doc }).result;
});
diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts
index f151acd81..ac524394a 100644
--- a/src/client/util/ScriptingGlobals.ts
+++ b/src/client/util/ScriptingGlobals.ts
@@ -1,8 +1,18 @@
+import ts from 'typescript';
-import * as ts from "typescript";
export { ts };
+const _scriptingGlobals: { [name: string]: any } = {};
+const _scriptingDescriptions: { [name: string]: any } = {};
+const _scriptingParams: { [name: string]: any } = {};
+// eslint-disable-next-line import/no-mutable-exports
+export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
export namespace ScriptingGlobals {
+ export function getGlobals() { return Object.keys(_scriptingGlobals); } // prettier-ignore
+ export function getGlobalObj() { return _scriptingGlobals; } // prettier-ignore
+ export function getDescriptions() { return _scriptingDescriptions; } // prettier-ignore
+ export function getParameters() { return _scriptingParams; } // prettier-ignore
+
export function add(global: { name: string }): void;
export function add(name: string, global: any): void;
export function add(global: { name: string }, decription?: string, params?: string): void;
@@ -11,7 +21,7 @@ export namespace ScriptingGlobals {
let obj: any;
if (second !== undefined) {
- if (typeof first === "string") {
+ if (typeof first === 'string') {
n = first;
obj = second;
} else {
@@ -22,18 +32,21 @@ export namespace ScriptingGlobals {
_scriptingParams[n] = third;
}
}
- } else if (first && typeof first.name === "string") {
+ } else if (first && typeof first.name === 'string') {
n = first.name;
obj = first;
} else {
- throw new Error("Must either register an object with a name, or give a name and an object");
+ throw new Error('Must either register an object with a name, or give a name and an object');
}
- if (n === undefined || n === "undefined") {
+ if (n === undefined || n === 'undefined') {
return false;
- } else if (_scriptingGlobals.hasOwnProperty(n)) {
+ }
+ // eslint-disable-next-line no-prototype-builtins
+ if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
_scriptingGlobals[n] = obj;
+ return true;
}
export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
return { ..._scriptingGlobals, ...(globals || {}) };
@@ -57,25 +70,16 @@ export namespace ScriptingGlobals {
return false;
}
- export function resetScriptingGlobals() { scriptingGlobals = _scriptingGlobals; }
+ export function resetScriptingGlobals() {
+ scriptingGlobals = _scriptingGlobals;
+ }
// const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]);
- export function printNodeType(node: any, indentation = "") { console.log(indentation + ts.SyntaxKind[node.kind]); }
-
- export function getGlobals() { return Object.keys(_scriptingGlobals); }
-
- export function getGlobalObj() { return _scriptingGlobals; }
-
- export function getDescriptions() { return _scriptingDescriptions; }
-
- export function getParameters() { return _scriptingParams; }
+ export function printNodeType(node: any, indentation = '') {
+ console.log(indentation + ts.SyntaxKind[node.kind]);
+ }
}
-export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
+export function scriptingGlobal(constructor: { new (...args: any[]): any }) {
ScriptingGlobals.add(constructor);
}
-
-const _scriptingGlobals: { [name: string]: any } = {};
-export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
-const _scriptingDescriptions: { [name: string]: any } = {};
-const _scriptingParams: { [name: string]: any } = {}; \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index fff2737b6..609fedfa9 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,5 +1,5 @@
import { ObservableMap } from 'mobx';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
@@ -8,16 +8,16 @@ import { DocOptions, FInfo } from '../documents/Documents';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
- export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
+ export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = matchKeyNames
? []
: Object.entries(DocOptions)
- .filter(([key, info]: [string, FInfo]) => !info?.searchable())
+ .filter(([, info]: [string, FInfo]) => !info?.searchable())
.map(([key]) => key);
- const exact = query.startsWith('=');
- query = query.toLowerCase().split('=').lastElement();
+ const exact = queryIn.startsWith('=');
+ const query = queryIn.toLowerCase().split('=').lastElement();
const results = new ObservableMap<Doc, string[]>();
if (collectionDoc) {
@@ -30,7 +30,7 @@ export namespace SearchUtil {
(onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
key =>
(val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
- matchKeyNames ? key : Field.toString(doc[key] as Field))
+ matchKeyNames ? key : Field.toString(doc[key] as FieldType))
&& hlights.add(key)
); // prettier-ignore
blockedKeys.forEach(key => hlights.delete(key));
@@ -51,7 +51,11 @@ export namespace SearchUtil {
*/
export function documentKeys(doc: Doc) {
const keys: { [key: string]: boolean } = {};
- Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
+ Doc.GetAllPrototypes(doc).map(proto =>
+ Object.keys(proto).forEach(key => {
+ keys[key] = false;
+ })
+ );
return Array.from(Object.keys(keys));
}
@@ -62,16 +66,18 @@ export namespace SearchUtil {
* This method iterates through an array of docs and all docs within those docs, calling
* the function func on each doc.
*/
- export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+ export function foreachRecursiveDoc(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+ let docs = docsIn;
let newarray: Doc[] = [];
- var depth = 0;
+ let depth = 0;
const visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
docs.filter(d => d && !visited.includes(d)).forEach(d => {
visited.push(d);
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
data && newarray.push(...DocListCast(data));
const sidebar = d[fieldKey + '_sidebar'];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..1328e90e9 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -11,6 +11,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
import { UndoManager } from './UndoManager';
export class SelectionManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SelectionManager;
private static get Instance() {
return SelectionManager._manager ?? new SelectionManager();
@@ -61,7 +62,9 @@ export class SelectionManager {
dv.IsSelected = false;
dv._props.whenChildContentsActiveChanged(false);
});
- runInAction(() => (this.Instance.SelectedViews.length = 0));
+ runInAction(() => {
+ this.Instance.SelectedViews.length = 0;
+ });
if (found) this.SelectView(found, false);
};
@@ -71,17 +74,20 @@ export class SelectionManager {
public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
if (Doc.noviceMode && expertMode) return false;
if (type === 'tab') {
return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
}
- let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+ const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deselectAll() {
SelectionManager.DeselectAll();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function undo() {
SelectionManager.DeselectAll();
return UndoManager.Undo();
@@ -89,17 +95,19 @@ ScriptingGlobals.add(function undo() {
export function ShowUndoStack() {
SelectionManager.DeselectAll();
- var buffer = '';
+ let buffer = '';
UndoManager.undoStack.forEach((batch, i) => {
buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
- ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
+ // /batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
});
alert(buffer);
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function redo() {
SelectionManager.DeselectAll();
return UndoManager.Redo();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
const docs = SelectionManager.Views.map(dv => dv.Document).filter(
d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 8daa69890..d9d22437c 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr';
-import { Field } from '../../fields/Doc';
+// import { Field } from '../../fields/Doc';
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -7,12 +7,16 @@ export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any,
cb(err, newValue);
serializing--;
}
+
+const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
+const reverseMap: { [ctor: string]: string } = {};
+
export namespace SerializationHelper {
export function IsSerializing() {
return serializing > 0;
}
- export function Serialize(obj: Field): any {
+ export function Serialize(obj: any /* Field */): any {
if (obj === undefined || obj === null) {
return null;
}
@@ -24,7 +28,7 @@ export namespace SerializationHelper {
serializing++;
if (!(obj.constructor.name in reverseMap)) {
serializing--;
- throw Error('Error: ' + `type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+ throw Error(`Error: type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
}
const json = serialize(obj);
@@ -52,30 +56,29 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
+ const value = await new Promise(res => {
+ deserialize(type.ctor, obj, (err, result) => res(result));
+ });
type.afterDeserialize?.(value);
return value;
}
}
-const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
-const reverseMap: { [ctor: string]: string } = {};
-
-export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
- function addToMap(className: string, ctor: { new (...args: any[]): any }) {
- const schema = getDefaultModelSchema(ctor) as any;
- if (schema.targetClass !== ctor || constructorArgs) {
- setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) });
+export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+ function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
+ const schema = getDefaultModelSchema(Ctor) as any;
+ if (schema.targetClass !== Ctor || constructorArgs) {
+ setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) });
}
if (!(className in serializationTypes)) {
- serializationTypes[className] = { ctor, afterDeserialize };
- reverseMap[ctor.name] = className;
+ serializationTypes[className] = { ctor: Ctor, afterDeserialize };
+ reverseMap[Ctor.name] = className;
} else {
throw new Error(`Name ${className} has already been registered as deserializable`);
}
}
- return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
+ return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor);
}
export function autoObject(): PropSchema {
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index c8df9182d..891561245 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,44 +1,28 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
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<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ServerStats;
@observable private isOpen = false; // whether the SharingManager modal is open or not
+ @observable _stats: { [key: string]: any } | undefined = undefined;
// private get linkVisible() {
// return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
// }
- @action
- public open = async () => {
- /**
- * Populates the list of users.
- */
- fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
-
- this.isOpen = true;
- };
-
- public close = action(() => {
- this.isOpen = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
ServerStats.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> {
);
}
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ });
+ public open = async () => {
+ /**
+ * Populates the list of users.
+ */
+ fetch('/stats').then((res: Response) =>
+ res.text().then(
+ action(stats => {
+ this._stats = JSON.parse(stats);
+ })
+ )
+ );
+
+ runInAction(() => {
+ this.isOpen = true;
+ });
+ };
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8594a1c92..8347844f7 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,11 +1,13 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsGoogle } from 'react-icons/bs';
import { FaFillDrip, FaPalette } from 'react-icons/fa';
-import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
+import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { DashVersion } from '../../fields/DocSymbols';
import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
@@ -17,7 +19,8 @@ import { MainViewModal } from '../views/MainViewModal';
import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
-import { undoBatch } from './UndoManager';
+import { SnappingManager } from './SnappingManager';
+import { undoable } from './UndoManager';
export enum ColorScheme {
Dark = 'Dark',
@@ -34,53 +37,127 @@ export enum freeformScrollMode {
@observer
export class SettingsManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
- @observable public isOpen = false;
- @observable private passwordResultText = '';
- @observable private playgroundMode = false;
+ @observable private _passwordResultText = '';
+ @observable private _playgroundMode = false;
- @observable private curr_password = '';
- @observable private new_password = '';
- @observable private new_confirm = '';
+ @observable private _curr_password = '';
+ @observable private _new_password = '';
+ @observable private _new_confirm = '';
@observable private _lastPressedSidebarBtn: Opt<Doc> = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
- @observable activeTab = 'Accounts';
+ @observable private _activeTab = 'Accounts';
+ @observable private _isOpen = false;
@observable public propertiesWidth: number = 0;
+ private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
+
+ public closeMgr = action(() => {
+ this._isOpen = false;
+ });
+ public openMgr = action(() => {
+ this._isOpen = true;
+ });
+
+ private matchSystem = undoable(() => {
+ if (Doc.UserDoc().userThemeSystem) {
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
+ if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
+ }
+ }, 'match system theme');
+ private setFreeformScrollMode = undoable((mode: string) => {
+ Doc.UserDoc().freeformScrollMode = mode;
+ }, 'set scroll mode');
+ private selectUserMode = undoable((mode: string) => {
+ Doc.noviceMode = mode === 'Novice';
+ }, 'change user mode');
+ private changeFontFamily = undoable((font: string) => {
+ Doc.UserDoc().fontFamily = font;
+ }, 'change font family');
+ private switchUserBackgroundColor = undoable((color: string) => {
+ Doc.UserDoc().userBackgroundColor = color;
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
+ }, 'change background color');
+ private switchUserColor = undoable((color: string) => {
+ Doc.UserDoc().userColor = color;
+ }, 'change user color');
+ switchUserVariantColor = undoable((color: string) => {
+ Doc.UserDoc().userVariantColor = color;
+ }, 'change variant color');
+ userThemeSystemToggle = undoable(() => {
+ Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
+ this.matchSystem();
+ }, 'change theme color');
+ playgroundModeToggle = undoable(
+ action(() => {
+ this._playgroundMode = !this._playgroundMode;
+ if (this._playgroundMode) {
+ DocServer.Control.makeReadOnly();
+ addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+ } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
+ }),
+ 'set playgorund mode'
+ );
+ changeColorScheme = undoable(
+ action((scheme: string) => {
+ Doc.UserDoc().userTheme = scheme;
+ switch (scheme) {
+ case ColorScheme.Light:
+ this.switchUserColor('#323232');
+ this.switchUserBackgroundColor('#DFDFDF');
+ this.switchUserVariantColor('#BDDDF5');
+ break;
+ case ColorScheme.Dark:
+ 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;
+ default:
+ }
+ }),
+ 'change color scheme'
+ );
+
constructor(props: {}) {
super(props);
makeObservable(this);
SettingsManager.Instance = this;
this.matchSystem();
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (Doc.UserDoc().userThemeSystem) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
}
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
});
+ reaction(
+ () => [SettingsManager.userBackgroundColor, SettingsManager.userColor, SettingsManager.userVariantColor],
+ ([back, user, variant]) => {
+ SnappingManager.userBackgroundColor = back;
+ SnappingManager.userVariantColor = variant;
+ SnappingManager.userColor = user;
+ },
+ { fireImmediately: true }
+ );
+ SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
}
- matchSystem = () => {
- if (Doc.UserDoc().userThemeSystem) {
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
- if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
- }
- };
-
- public close = action(() => (this.isOpen = false));
- public open = action(() => (this.isOpen = true));
- private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
- private changePassword = async () => {
- if (!(this.curr_password && this.new_password && this.new_confirm)) {
- runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
- } else {
- const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
- const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
- runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
- }
- };
+ public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
+ public set LastPressedBtn(state:Doc|undefined) { this._lastPressedSidebarBtn = state; } // prettier-ignore
@computed public static get userColor() {
return StrCast(Doc.UserDoc().userColor);
@@ -94,60 +171,6 @@ export class SettingsManager extends React.Component<{}> {
return StrCast(Doc.UserDoc().userBackgroundColor);
}
- public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
- public SetLastPressedBtn = (state?:Doc) => runInAction(() => (this._lastPressedSidebarBtn = state)); // prettier-ignore
-
- @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((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 userThemeSystemToggle = action(() => {
- Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
- this.matchSystem();
- });
- @undoBatch playgroundModeToggle = action(() => {
- this.playgroundMode = !this.playgroundMode;
- if (this.playgroundMode) {
- DocServer.Control.makeReadOnly();
- addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
- } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
- });
-
- @undoBatch
- changeColorScheme = action((scheme: string) => {
- Doc.UserDoc().userTheme = scheme;
- switch (scheme) {
- case ColorScheme.Light:
- this.switchUserColor('#323232');
- this.switchUserBackgroundColor('#DFDFDF');
- this.switchUserVariantColor('#BDDDF5');
- break;
- case ColorScheme.Dark:
- 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;
- }
- });
-
@computed get colorsContent() {
const schemeMap = Array.from(Object.keys(ColorScheme));
const userTheme = StrCast(Doc.UserDoc().userTheme);
@@ -176,7 +199,7 @@ export class SettingsManager extends React.Component<{}> {
{userTheme === ColorScheme.Custom && (
<Group formLabel="Custom Theme">
<ColorPicker
- tooltip={'User Color'} //
+ tooltip="User Color" //
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaFillDrip />}
@@ -185,7 +208,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserColor}
/>
<ColorPicker
- tooltip={'User Background Color'}
+ tooltip="User Background Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -194,7 +217,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserBackgroundColor}
/>
<ColorPicker
- tooltip={'User Variant Color'}
+ tooltip="User Variant Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -212,64 +235,78 @@ export class SettingsManager extends React.Component<{}> {
return (
<div className="prefs-content">
<Toggle
- formLabel={'Show document header'}
- formLabelPlacement={'right'}
+ formLabel="Show document header"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
+ onClick={() => {
+ Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date';
+ }}
toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Show Full Toolbar'}
- formLabelPlacement={'right'}
+ 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'])}
+ onClick={() => {
+ Doc.UserDoc().documentLinksButton_fullMenu = !Doc.UserDoc().documentLinksButton_fullMenu;
+ }}
+ toggleStatus={BoolCast(Doc.UserDoc().documentLinksButton_fullMenu)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Show Button Labels'}
- formLabelPlacement={'right'}
+ formLabel="Show Button Labels"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
+ onClick={() => {
+ FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels;
+ }}
toggleStatus={FontIconBox.ShowIconLabels}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Recognize Ink Gestures'}
- formLabelPlacement={'right'}
+ formLabel="Recognize Ink Gestures"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
+ onClick={() => {
+ GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures;
+ }}
toggleStatus={GestureOverlay.RecognizeGestures}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Hide Labels In Ink Shapes'}
- formLabelPlacement={'right'}
+ formLabel="Hide Labels In Ink Shapes"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+ onClick={() => {
+ Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Open Ink Docs in Lightbox'}
- formLabelPlacement={'right'}
+ formLabel="Open Ink Docs in Lightbox"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+ onClick={() => {
+ Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Show Link Lines'}
- formLabelPlacement={'right'}
+ formLabel="Show Link Lines"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+ onClick={() => {
+ Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
size={Size.XSMALL}
color={SettingsManager.userColor}
@@ -278,13 +315,15 @@ export class SettingsManager extends React.Component<{}> {
<NumberDropdown
number={NumCast(Doc.UserDoc().headerHeight, 30)}
color={SettingsManager.userColor}
- numberDropdownType={'slider'}
+ numberDropdownType="slider"
min={6}
max={60}
step={2}
type={Type.TERT}
- unit={'px'}
- setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().headerHeight = val;
+ }}
/>
</Group>
</div>
@@ -308,7 +347,6 @@ export class SettingsManager extends React.Component<{}> {
@computed get textContent() {
const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
- const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
return (
<div className="tab-content appearances-content">
@@ -316,7 +354,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Text</div>
<div className="tab-column-content">
{/* <NumberInput/> */}
- <Group formLabel={'Default Font'}>
+ <Group formLabel="Default Font">
<NumberDropdown
color={SettingsManager.userColor}
numberDropdownType="slider"
@@ -325,20 +363,20 @@ export class SettingsManager extends React.Component<{}> {
step={2}
type={Type.PRIM}
number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
- unit={'px'}
- setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().fontSize = val + 'px';
+ }}
/>
<Dropdown
- items={fontFamilies.map(val => {
- return {
- text: val,
- val: val,
- style: {
- fontFamily: val,
- },
- };
- })}
- closeOnSelect={true}
+ items={fontFamilies.map(val => ({
+ text: val,
+ val: val,
+ style: {
+ fontFamily: val,
+ },
+ }))}
+ closeOnSelect
dropdownType={DropdownType.SELECT}
type={Type.TERT}
selectedVal={StrCast(Doc.UserDoc().fontFamily)}
@@ -355,30 +393,15 @@ export class SettingsManager extends React.Component<{}> {
);
}
- @action
- changeVal = (value: string, pass: string) => {
- switch (pass) {
- case 'curr':
- this.curr_password = value;
- break;
- case 'new':
- this.new_password = value;
- break;
- case 'conf':
- this.new_confirm = value;
- break;
- }
- };
-
@computed get passwordContent() {
return (
<div className="password-content">
- <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
- <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
- <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.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={SettingsManager.userColor} />
- <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} />
+ <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+ <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+ <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.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={SettingsManager.userColor} />
+ <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} />
</div>
);
}
@@ -386,7 +409,7 @@ export class SettingsManager extends React.Component<{}> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <Button type={Type.TERT} text={'Connect to Google'} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
+ <Button type={Type.TERT} text="Connect to Google" iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
</div>
);
}
@@ -406,10 +429,6 @@ export class SettingsManager extends React.Component<{}> {
);
}
- setFreeformScrollMode = (mode: string) => {
- Doc.UserDoc().freeformScrollMode = mode;
- };
-
@computed get modesContent() {
return (
<div className="tab-content modes-content">
@@ -417,8 +436,8 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Modes</div>
<div className="tab-column-content">
<Dropdown
- formLabel={'Mode'}
- closeOnSelect={true}
+ formLabel="Mode"
+ closeOnSelect
items={[
{
text: 'Novice',
@@ -442,7 +461,7 @@ export class SettingsManager extends React.Component<{}> {
color={SettingsManager.userColor}
fillWidth
/>
- <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
+ <Toggle formLabel="Playground Mode" toggleType={ToggleType.SWITCH} toggleStatus={this._playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
</div>
<div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
Freeform Navigation
@@ -450,7 +469,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<Dropdown
formLabel="Scroll Mode"
- closeOnSelect={true}
+ closeOnSelect
items={[
{
text: 'Scroll to Pan',
@@ -475,16 +494,34 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column">
<div className="tab-column-title">Permissions</div>
<div className="tab-column-content">
- <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
+ <Button text="Manage Groups" type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
<Toggle
toggleType={ToggleType.SWITCH}
- formLabel={'Default access private'}
+ formLabel="Default access private"
color={SettingsManager.userColor}
toggleStatus={BoolCast(Doc.defaultAclPrivate)}
- onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
+ onClick={action(() => {
+ Doc.defaultAclPrivate = !Doc.defaultAclPrivate;
+ })}
+ />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel="Enable Sharing UI"
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.IsSharingEnabled)}
+ onClick={action(() => {
+ Doc.IsSharingEnabled = !Doc.IsSharingEnabled;
+ })}
+ />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel="Disable Info UI"
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.IsInfoUIDisabled)}
+ onClick={action(() => {
+ Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled;
+ })}
/>
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Enable Sharing UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Disable Info UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsInfoUIDisabled)} onClick={action(() => (Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled))} />
</div>
</div>
</div>
@@ -506,7 +543,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-panel" style={{ background: SettingsManager.userColor }}>
<div className="settings-tabs">
{tabs.map(tab => {
- const isActive = this.activeTab === tab.title;
+ const isActive = this._activeTab === tab.title;
return (
<div
key={tab.title}
@@ -515,7 +552,9 @@ export class SettingsManager extends React.Component<{}> {
color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor,
}}
className={'tab-control ' + (isActive ? 'active' : 'inactive')}
- onClick={action(() => (this.activeTab = tab.title))}>
+ onClick={action(() => {
+ this._activeTab = tab.title;
+ })}>
{tab.title}
</div>
);
@@ -525,19 +564,19 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-user">
<div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div>
<div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
- {Doc.CurrentUserEmail}
+ {ClientUtils.CurrentUserEmail()}
</div>
- <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
+ <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
</div>
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.closeMgr} color={SettingsManager.userColor} />
</div>
<div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
{tabs.map(tab => (
- <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
+ <div key={tab.title} className={'tab-section ' + (this._activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
</div>
))}
@@ -546,13 +585,37 @@ export class SettingsManager extends React.Component<{}> {
);
}
+ private changePassword = async () => {
+ if (!(this._curr_password && this._new_password && this._new_confirm)) {
+ runInAction(() => {
+ this._passwordResultText = "Error: Hey, we're missing some fields!";
+ });
+ } else {
+ const passwordBundle = { curr_pass: this._curr_password, new_pass: this._new_password, new_confirm: this._new_confirm };
+ const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ runInAction(() => {
+ this._passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!';
+ });
+ }
+ };
+
+ @action
+ changeVal = (value: string, pass: string) => {
+ switch (pass) {
+ case 'curr': this._curr_password = value; break;
+ case 'new': this._new_password = value; break;
+ case 'conf': this._new_confirm = value; break;
+ default:
+ } // prettier-ignore
+ };
+
render() {
return (
<MainViewModal
contents={this.settingsInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
+ isDisplayed={this._isOpen}
+ interactive
+ closeOnExternalClick={this.closeMgr}
dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fddf735e3..5b4ac5aff 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { concat, intersection } from 'lodash';
@@ -6,13 +9,14 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
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 { StrCast } from '../../fields/Types';
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
-import { Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util';
import { DocServer } from '../DocServer';
import { DictationOverlay } from '../views/DictationOverlay';
import { MainViewModal } from '../views/MainViewModal';
@@ -64,59 +68,31 @@ interface ValidatedUser {
@observer
export class SharingManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SharingManager;
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+ private populating: boolean = false; // whether the list of users is populating or not
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable public users: ValidatedUser[] = []; // the list of users with sharing docs
@observable private targetDoc: Doc | undefined = undefined; // the document being shared
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
// @observable private copied = false;
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
@observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
@observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
- private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share 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;
// }
- public open = (target?: DocumentView, target_doc?: Doc) => {
- this.populateUsers();
- runInAction(() => {
- this.targetDocView = target;
- this.targetDoc = target_doc || target?.Document;
- DictationOverlay.Instance.hasActiveModal = true;
- this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Augment;
- this.upgradeNested = true;
- });
- };
-
- public close = action(() => {
- this.isOpen = false;
- this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
- TaskCompletionBox.taskCompleted = false;
- setTimeout(
- action(() => {
- // this.copied = false;
- DictationOverlay.Instance.hasActiveModal = false;
- this.targetDoc = undefined;
- }),
- 500
- );
- this.layoutDocAcls = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
@@ -131,223 +107,6 @@ export class SharingManager extends React.Component<{}> {
}
/**
- * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
- */
- populateUsers = async () => {
- if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
- 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) => {
- const sharingDoc = docs[newUser.sharingDocumentId];
- const linkDatabase = docs[newUser.linkDatabaseId];
- 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(linkDatabase);
- }
- }
- })
- );
- this.populating = false;
- }
- };
-
- /**
- * Shares the document with a user.
- */
- 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 docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- 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 = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
-
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- 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) {
- Doc.AddDocToList(group, 'docsShared', doc);
-
- 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.
- * @param group
- * @param emailId
- */
- shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
- const user = this.users.find(({ user: { email } }) => email === emailId)!;
- const self = this;
- if (group.docsShared) {
- if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
- else {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- 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);
- })
- );
- }
- }
- };
-
- /**
- * Called from the properties sidebar to change permissions of a user.
- */
- 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, undefined, true);
- });
- } else {
- docs.forEach(doc => {
- 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.
- * @param group
- * @param emailId
- */
- removeMember = (group: Doc, emailId: string) => {
- const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
-
- 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);
- })
- );
- }
- };
-
- /**
- * Removes a group's permissions from documents that have been shared with it.
- * @param group
- */
- removeGroup = (group: Doc) => {
- if (group.docsShared) {
- DocListCast(group.docsShared).forEach(doc => {
- const acl = `acl-${StrCast(group.title)}`;
- distributeAcls(acl, SharingPermissions.None, doc);
-
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
-
- users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
- });
- }
- };
-
- // private setExternalSharing = (permission: string) => {
- // const targetDoc = this.targetDoc;
- // if (!targetDoc) {
- // return;
- // }
- // targetDoc["acl-" + PublicKey] = permission;
- // }s
-
- /**
- * 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, showGuestOptions?: boolean) {
- const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift('-multiple-');
- return dropdownValues.map(permission => (
- <option key={permission} value={permission}>
- {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
- </option>
- ));
- }
-
- private focusOn = (contents: string) => {
- const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
- const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
- return (
- <span
- className="focus-span"
- title={title}
- onClick={() => {
- if (this.targetDoc && this.targetDocView && docs.length === 1) {
- DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
- }
- }}
- onPointerEnter={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.BrushDoc(doc));
- this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
- }
- })}
- onPointerLeave={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
- this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
- }
- })}>
- {contents}
- </span>
- );
- };
-
- /**
* Handles changes in the users selected in react-select
*/
@action
@@ -366,57 +125,6 @@ export class SharingManager extends React.Component<{}> {
);
/**
- * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
- */
- 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);
- }
- });
-
- if (this.shareDocumentButtonRef.current) {
- 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.layoutDocAcls = false;
- this.selectedUsers = null;
- }
- }),
- 'share Doc'
- );
-
- /**
- * Sorting algorithm to sort users.
- */
- sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
- const { email: e1 } = u1.user;
- const { email: e2 } = u2.user;
- return e1 < e2 ? -1 : e1 === e2 ? 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 main interface of the SharingManager.
*/
@computed get sharingInterface() {
@@ -474,8 +182,8 @@ export class SharingManager extends React.Component<{}> {
permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
return !permissions ? null : (
- <div key={userKey} className={'container'}>
- <span className={'padding'}>{user.email}</span>
+ <div key={userKey} className="container">
+ <span className="padding">{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
@@ -496,21 +204,21 @@ 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 userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
const curUserPermission = StrCast(targetDoc[userKey]);
// const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
- <div key={'owner'} className={'container'}>
- <span className="padding">{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
+ <div key="owner" className="container">
+ <span className="padding">{targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>Owner</div>
+ <div className="permissions-dropdown">Owner</div>
</div>
</div>
) : null,
- sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
- <div key={'me'} className={'container'}>
- <span className={'padding'}>Me</span>
+ sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
+ <div key="me" className="container">
+ <span className="padding">Me</span>
<div className="edit-actions">
<div className={`permissions-dropdown-${curUserPermission}`}>
{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
@@ -523,18 +231,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: 'Guest' }); //, { title: "ALL" });
+ groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- let groupKey = `acl-${StrCast(group.title)}`;
+ const 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'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- <div className={'padding'}>{StrCast(group.title)}</div>
+ <div key={groupKey} className="container" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="padding">{StrCast(group.title)}</div>
&nbsp;
- {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
- <div className={'edit-actions'}>
+ {group instanceof Doc ? (
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={SettingsManager.userColor}
+ onClick={action(() => {
+ GroupManager.Instance.currentGroup = group;
+ })}
+ />
+ ) : null}
+ <div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
{this.sharingOptions(uniform, group.title === 'Guest')}
@@ -551,7 +268,14 @@ 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}
+ {GroupManager.Instance?.currentGroup ? (
+ <GroupMemberView
+ group={GroupManager.Instance.currentGroup}
+ onCloseButtonClick={action(() => {
+ GroupManager.Instance.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div
className="sharing-contents"
style={{
@@ -560,16 +284,16 @@ export class SharingManager extends React.Component<{}> {
}}>
<p className="share-title" style={{ color: SettingsManager.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')} />
+ <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="share-copy-link">
- <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SettingsManager.userColor} />
</div>
{admin ? (
<div className="share-container">
@@ -611,19 +335,45 @@ export class SharingManager extends React.Component<{}> {
</select>
</div>
<div className="share-button">
- <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+ <Button text="SHARE" type={Type.TERT} color={SettingsManager.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>
- <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showUserOptions = !this.showUserOptions;
+ })}
+ />{' '}
+ <label style={{ marginRight: 10 }}>Individuals</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showGroupOptions = !this.showGroupOptions;
+ })}
+ />{' '}
+ <label>Groups</label>
</div>
<div className="acl-container">
{Doc.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>
+ <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>
@@ -632,14 +382,25 @@ export class SharingManager extends React.Component<{}> {
<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>
+ <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'))}>
+ <div className="individual-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-individual">
Individuals
<IconButton
@@ -651,11 +412,15 @@ export class SharingManager extends React.Component<{}> {
</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'))}>
+ <div className="group-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
<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="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}
@@ -663,7 +428,7 @@ export class SharingManager extends React.Component<{}> {
/>
</div>
</div>
- <div className={'groups-list'}>{groupListContents}</div>
+ <div className="groups-list">{groupListContents}</div>
</div>
</div>
</div>
@@ -671,7 +436,309 @@ export class SharingManager extends React.Component<{}> {
);
}
+ /**
+ * Shares the document with a user.
+ */
+ 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 docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+ 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 = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
+
+ const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+ 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) {
+ Doc.AddDocToList(group, 'docsShared', doc);
+
+ 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');
+ /**
+ * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+ */
+ populateUsers = async () => {
+ if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
+ this.populating = true;
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.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) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
+ 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(linkDatabase);
+ }
+ }
+ })
+ );
+ this.populating = false;
+ }
+ };
+
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
+ TaskCompletionBox.taskCompleted = false;
+ setTimeout(
+ action(() => {
+ // this.copied = false;
+ DictationOverlay.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
+ this.layoutDocAcls = false;
+ });
+
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
+ this.populateUsers();
+ runInAction(() => {
+ this.targetDocView = target;
+ this.targetDoc = targetDoc || target?.Document;
+ DictationOverlay.Instance.hasActiveModal = true;
+ this.isOpen = this.targetDoc !== undefined;
+ this.permissions = SharingPermissions.Augment;
+ this.upgradeNested = true;
+ });
+ };
+
+ /**
+ * Shares the documents shared with a group with a new user who has been added to that group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
+ if (group.docsShared) {
+ if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
+ else {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ 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);
+ })
+ );
+ }
+ }
+ };
+
+ /**
+ * Called from the properties sidebar to change permissions of a user.
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ 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' ? ClientUtils.CurrentUserEmail() : shareWith));
+ docs.forEach(doc => {
+ if (user) this.setInternalSharing(user, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
+ });
+ } else {
+ docs.forEach(doc => {
+ 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.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
+
+ 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);
+ })
+ );
+ }
+ };
+
+ /**
+ * Removes a group's permissions from documents that have been shared with it.
+ * @param group
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeGroup = (group: Doc) => {
+ if (group.docsShared) {
+ DocListCast(group.docsShared).forEach(doc => {
+ const acl = `acl-${StrCast(group.title)}`;
+ distributeAcls(acl, SharingPermissions.None, doc);
+
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+
+ users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
+ });
+ }
+ };
+
+ // private setExternalSharing = (permission: string) => {
+ // const targetDoc = this.targetDoc;
+ // if (!targetDoc) {
+ // return;
+ // }
+ // targetDoc["acl-" + PublicKey] = permission;
+ // }s
+
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = () => {
+ ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
+ };
+
+ private focusOn = (contents: string) => {
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+ const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
+ return (
+ <span
+ className="focus-span"
+ title={title}
+ onClick={() => {
+ if (this.targetDoc && this.targetDocView && docs.length === 1) {
+ DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
+ this.dialogueBoxOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ }
+ })}>
+ {contents}
+ </span>
+ );
+ };
+
+ /**
+ * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
+ */
+ 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);
+ }
+ });
+
+ if (this.shareDocumentButtonRef.current) {
+ 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.layoutDocAcls = false;
+ this.selectedUsers = null;
+ }
+ }),
+ 'share Doc'
+ );
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+ const { email: e1 } = u1.user;
+ const { email: e2 } = u2.user;
+ return e1 < e2 ? -1 : e1 === e2 ? 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 SharingPermissions (Admin, Can Edit etc) access that's used to share
+ */
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
+ }
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 359140732..3da85191f 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,7 +1,7 @@
-import { observable, action, runInAction, reaction, makeObservable } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
+import { observable, action, runInAction, makeObservable } from 'mobx';
export class SnappingManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SnappingManager;
private static get Instance() {
return SnappingManager._manager ?? new SnappingManager();
@@ -10,9 +10,10 @@ export class SnappingManager {
@observable _shiftKey = false;
@observable _ctrlKey = false;
@observable _metaKey = false;
+ @observable _showPresPaths = false;
@observable _isLinkFollowing = false;
@observable _isDragging: boolean = false;
- @observable _isResizing: Doc | undefined = undefined;
+ @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized
@observable _canEmbed: boolean = false;
@observable _horizSnapLines: number[] = [];
@observable _vertSnapLines: number[] = [];
@@ -23,7 +24,9 @@ export class SnappingManager {
makeObservable(this);
}
- @action public static clearSnapLines = () => (this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0);
+ @action public static clearSnapLines = () => {
+ this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0;
+ };
@action public static addSnapLines = (horizLines: number[], vertLines: number[]) => {
this.Instance._horizSnapLines.push(...horizLines);
this.Instance._vertSnapLines.push(...vertLines);
@@ -34,17 +37,24 @@ export class SnappingManager {
public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore
public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore
+ public static get ShowPresPaths() { return this.Instance._showPresPaths; } // prettier-ignore
public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore
public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore
public static get CanEmbed() { return this.Instance._canEmbed; } // prettier-ignore
public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore
- public static SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
- public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore
- public static SetMetaKey = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore
- public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
- public static SetIsDragging = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore
- public static SetIsResizing = (doc: Opt<Doc>) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore
- public static SetCanEmbed = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore
- public static SetExploreMode = (state:boolean) => runInAction(() => (this.Instance._exploreMode = state)); // prettier-ignore
+ public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
+ public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
+ public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+ public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
+ public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
+ public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
+ public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore
+ public static SetCanEmbed = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore
+ public static SetExploreMode = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore
+
+ public static userColor: string | undefined;
+ public static userVariantColor: string | undefined;
+ public static userBackgroundColor: string | undefined;
+ public static SettingsStyle: any;
}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f9c2d522f..25a3c9ad8 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,8 +1,7 @@
-import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { NumCast } from '../../fields/Types';
import { Doc, DocListCast } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../fields/FieldSymbols';
import { CollectionViewType } from '../documents/DocumentTypes';
export type Movement = {
@@ -33,6 +32,7 @@ export class TrackMovements {
private tabChangeDisposeFunc: IReactionDisposer | null;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: TrackMovements;
static get Instance(): TrackMovements {
return TrackMovements._instance;
@@ -92,12 +92,13 @@ export class TrackMovements {
// so that the size comparisons are correct, we must filter to only the FFViews
const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
const tabbedFFViews = new Set<Doc>();
- for (const DashDoc of tabbedDocs) {
+ tabbedDocs.forEach(DashDoc => {
if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
- }
+ });
// new tab was added - need to add it
if (tabbedFFViews.size > this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const DashDoc of tabbedDocs) {
if (!this.recordingFFViews.has(DashDoc)) {
if (isFFView(DashDoc)) {
@@ -111,6 +112,7 @@ export class TrackMovements {
}
// tab was removed - need to remove it from recordingFFViews
else if (tabbedFFViews.size < this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const [doc] of this.recordingFFViews) {
if (!tabbedFFViews.has(doc)) {
this.removeRecordingFFView(doc);
@@ -208,11 +210,11 @@ export class TrackMovements {
return;
}
- for (const [id, disposeFunc] of this.recordingFFViews) {
+ Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => {
// console.info('calling dispose func : docId', id);
disposeFunc();
- this.recordingFFViews.delete(id);
- }
+ this.recordingFFViews?.delete(id);
+ });
};
private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
@@ -241,9 +243,9 @@ export class TrackMovements {
// method that concatenates an array of presentatations into one
public concatPresentations = (presentations: Presentation[]): Presentation => {
// these three will lead to the combined presentation
- let combinedMovements: Movement[] = [];
+ const combinedMovements: Movement[] = [];
let sumTime = 0;
- let combinedMetas: any[] = [];
+ const combinedMetas: any[] = [];
presentations.forEach(presentation => {
const { movements, totalTime, meta } = presentation;
@@ -251,9 +253,7 @@ export class TrackMovements {
// update movements if they had one
if (movements) {
// add the summed time to the movements
- const addedTimeMovements = movements.map(move => {
- return { ...move, time: move.time + sumTime };
- });
+ const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime }));
// concat the movements already in the combined presentation with these new ones
combinedMovements.push(...addedTimeMovements);
}
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index dca37c960..1a07dd6ae 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -116,20 +116,14 @@ export class Transform {
preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
- transformPoint = (x: number, y: number): [number, number] => {
- x *= this._scale;
- x += this._translateX;
- y *= this._scale;
- y += this._translateY;
- return [x, y];
- };
+ transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY];
transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
- [x, y] = this.transformPoint(x, y);
- [width, height] = this.transformDirection(width, height);
- return { x, y, width, height };
+ const [tx, ty] = this.transformPoint(x, y);
+ const [twidth, theight] = this.transformDirection(width, height);
+ return { x: tx, y: ty, width: twidth, height: theight };
}
inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 90fd299c1..9ef2aa8d7 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -14,27 +14,27 @@ export class TypedEvent<T> {
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {
- dispose: () => this.off(listener)
+ dispose: () => this.off(listener),
};
- }
+ };
once = (listener: Listener<T>): void => {
this.listenersOncer.push(listener);
- }
+ };
off = (listener: Listener<T>) => {
const callbackIndex = this.listeners.indexOf(listener);
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
- }
+ };
emit = (event: T) => {
/** Update any general listeners */
- this.listeners.forEach((listener) => listener(event));
+ this.listeners.forEach(listener => listener(event));
/** Clear the `once` queue */
- this.listenersOncer.forEach((listener) => listener(event));
+ this.listenersOncer.forEach(listener => listener(event));
this.listenersOncer = [];
- }
+ };
- pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
-} \ No newline at end of file
+ pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
+}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 857ca852f..956c0e674 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,11 @@
-import { observable, action, runInAction } from 'mobx';
-import { Doc, Field } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+/* eslint-disable prefer-spread */
+/* eslint-disable no-use-before-define */
+import { action, observable, runInAction } from 'mobx';
import { Without } from '../../Utils';
+import { RichTextField } from '../../fields/RichTextField';
+
+// eslint-disable-next-line prefer-const
+let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
function getBatchName(target: any, key: string | symbol): string {
const keyName = key.toString();
@@ -37,10 +41,11 @@ function propertyDecorator(target: any, key: string | symbol) {
}
export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any {
- return function () {
+ return function (...fargs) {
const batch = UndoManager.StartBatch(batchName);
try {
- return fn.apply(undefined, arguments as any);
+ // eslint-disable-next-line prefer-rest-params
+ return fn.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -48,13 +53,15 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar
}
export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
if (!key) {
- return function () {
+ return function (...fargs: any[]) {
const batch = UndoManager.StartBatch('');
try {
- return target.apply(undefined, arguments);
+ return target.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -62,7 +69,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
}
if (!descriptor) {
propertyDecorator(target, key);
- return;
+ return undefined;
}
const oldFunction = descriptor.value;
@@ -86,24 +93,28 @@ export namespace UndoManager {
}
type UndoBatch = UndoEvent[];
- export let undoStackNames: string[] = observable([]);
- export let redoStackNames: string[] = observable([]);
- export let undoStack: UndoBatch[] = observable([]);
- export let redoStack: UndoBatch[] = observable([]);
let currentBatch: UndoBatch | undefined;
- export let batchCounter = observable.box(0);
let undoing = false;
- export let tempEvents: UndoEvent[] | undefined = undefined;
+ let tempEvents: UndoEvent[] | undefined;
+ export const undoStackNames: string[] = observable([]);
+ export const redoStackNames: string[] = observable([]);
+ export const undoStack: UndoBatch[] = observable([]);
+ export const redoStack: UndoBatch[] = observable([]);
+ export const batchCounter = observable.box(0);
+ let _fieldPrinter: (val: any) => string = val => val?.toString();
+ export function SetFieldPrinter(printer: (val: any) => string) {
+ _fieldPrinter = printer;
+ }
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- Doc.MyDockedBtns.linearView_IsOpen &&
+ printToConsole &&
console.log(
' '.slice(0, batchCounter.get()) +
'UndoEvent : ' +
event.prop +
- ' = ' +
- (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+ ' = ' + // prettier-ignore
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(_fieldPrinter).join(',') : _fieldPrinter(value))
);
currentBatch.push(event);
tempEvents?.push(event);
@@ -129,21 +140,22 @@ export namespace UndoManager {
}
export function FilterBatches(fieldTypes: string[]) {
const fieldCounts: { [key: string]: number } = {};
- const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement();
+ const lastStack = UndoManager.undoStack.slice(-1)[0]; // .lastElement();
if (lastStack) {
- lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
+ lastStack.forEach(ev => {
+ fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1);
+ });
const fieldCount2: { [key: string]: number } = {};
- runInAction(
- () =>
- (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
- if (fieldTypes.includes(ev.prop)) {
- fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
- if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
- return false;
- }
- return true;
- }))
- );
+ runInAction(() => {
+ UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+ if (fieldTypes.includes(ev.prop)) {
+ fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+ if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+ return false;
+ }
+ return true;
+ });
+ });
}
}
export function TraceOpenBatches() {
@@ -160,11 +172,10 @@ export namespace UndoManager {
if (this.disposed) {
console.log('WARNING: undo batch already disposed');
return false;
- } else {
- this.disposed = true;
- openBatches.splice(openBatches.indexOf(this));
- return EndBatch(this.batchName, cancel);
}
+ this.disposed = true;
+ openBatches.splice(openBatches.indexOf(this));
+ return EndBatch(this.batchName, cancel);
};
end = () => this.dispose(false);
@@ -172,7 +183,7 @@ export namespace UndoManager {
}
export function StartBatch(batchName: string): Batch {
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
runInAction(() => batchCounter.set(batchCounter.get() + 1));
if (currentBatch === undefined) {
currentBatch = [];
@@ -182,7 +193,7 @@ export namespace UndoManager {
const EndBatch = action((batchName: string, cancel: boolean = false) => {
runInAction(() => batchCounter.set(batchCounter.get() - 1));
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')');
if (batchCounter.get() === 0 && currentBatch?.length) {
if (!cancel) {
undoStack.push(currentBatch);
@@ -199,10 +210,10 @@ export namespace UndoManager {
export function StartTempBatch() {
tempEvents = [];
}
- export function EndTempBatch<T>(success: boolean) {
+ export function EndTempBatch(success: boolean) {
UndoManager.UndoTempBatch(success);
}
- //TODO Make this return the return value
+ // TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
const batch = StartBatch(batchName);
try {
@@ -234,9 +245,11 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = commands.length - 1; i >= 0; i--) {
- commands[i].undo();
- }
+ // eslint-disable-next-line prettier/prettier
+ commands
+ .slice()
+ .reverse()
+ .forEach(command => command.undo());
undoing = false;
redoStackNames.push(names ?? '???');
@@ -255,9 +268,7 @@ export namespace UndoManager {
}
undoing = true;
- for (const command of commands) {
- command.redo();
- }
+ commands.forEach(command => command.redo());
undoing = false;
undoStackNames.push(names ?? '???');
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 8fc6de6f9..d6f3f2340 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,4 +1,8 @@
-import { Point } from "../../pen-gestures/ndollar";
+/* eslint-disable no-use-before-define */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+/* eslint-disable camelcase */
+import { Point } from '../../pen-gestures/ndollar';
class SmartRect {
minx: number = 0;
@@ -6,20 +10,43 @@ class SmartRect {
maxx: number = 0;
maxy: number = 0;
- constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) { this.minx = mix; this.miny = miy; this.maxx = max; this.maxy = may; }
+ constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) {
+ this.minx = mix;
+ this.miny = miy;
+ this.maxx = max;
+ this.maxy = may;
+ }
- public get Center() { return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0); }
- public get TopLeft() { return new Point(this.minx, this.miny); }
- public get TopRight() { return new Point(this.maxx, this.miny); }
- public get BotLeft() { return new Point(this.minx, this.maxy); }
- public get BotRight() { return new Point(this.maxx, this.maxy); }
- public get Width() { return this.maxx - this.minx; }
- public get Height() { return this.maxy - this.miny; }
- public static Intersect(a: SmartRect, b: SmartRect) { return a.Intersect(b); }
- public Intersect(b: SmartRect) { return !((this.minx > b.maxx) || (this.miny > b.maxy) || (b.minx > this.maxx) || (b.miny > this.maxy)); }
+ public get Center() {
+ return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0);
+ }
+ public get TopLeft() {
+ return new Point(this.minx, this.miny);
+ }
+ public get TopRight() {
+ return new Point(this.maxx, this.miny);
+ }
+ public get BotLeft() {
+ return new Point(this.minx, this.maxy);
+ }
+ public get BotRight() {
+ return new Point(this.maxx, this.maxy);
+ }
+ public get Width() {
+ return this.maxx - this.minx;
+ }
+ public get Height() {
+ return this.maxy - this.miny;
+ }
+ public static Intersect(a: SmartRect, b: SmartRect) {
+ return a.Intersect(b);
+ }
+ public Intersect(b: SmartRect) {
+ return !(this.minx > b.maxx || this.miny > b.maxy || b.minx > this.maxx || b.miny > this.maxy);
+ }
public ContainsPercentage(other: SmartRect, axis: Point) {
- var ret = 0;
+ let ret = 0;
const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
@@ -36,7 +63,7 @@ class SmartRect {
if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
- for (const pt of p) {
+ p.forEach(pt => {
if (pt.X < r.minx) {
r.minx = pt.X;
} else if (pt.X > r.maxx) {
@@ -47,7 +74,7 @@ class SmartRect {
} else if (pt.Y > r.maxy) {
r.maxy = pt.Y;
}
- }
+ });
}
return r;
}
@@ -64,18 +91,18 @@ export function Normalize(p: Point) {
function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
const uPrime = new Array<number>(last - first + 1); // New parameter values
- for (var i = first; i <= last; i++) {
+ for (let i = first; i <= last; i++) {
uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
}
return uPrime;
}
function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
- var maxError = 0; // Maximum error
- var splitPoint2D = (last - first + 1) / 2;
- for (var i = first + 1; i < last; i++) {
- const P = [0, 0]; // point on curve
+ let maxError = 0; // Maximum error
+ let splitPoint2D = (last - first + 1) / 2;
+ for (let i = first + 1; i < last; i++) {
+ const P = [0, 0]; // point on curve
EvalBezierFast(bezCurve, u[i - first], P);
- const dx = P[0] - d[i].X;// offset from point to curve
+ const dx = P[0] - d[i].X; // offset from point to curve
const dy = P[1] - d[i].Y;
const dist = Math.sqrt(dx * dx + dy * dy); // Current error
if (dist >= maxError) {
@@ -88,11 +115,11 @@ function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Poin
return { maxError, splitPoint2D };
}
function ChordLengthParameterize(d: Point[], first: number, last: number) {
- const u = new Array<number>(last - first + 1);// Parameterization
+ const u = new Array<number>(last - first + 1); // Parameterization
- var prev = 0.0;
+ let prev = 0.0;
u[0] = prev;
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
const lastd = d[i - 1];
const curd = d[i];
const dx = lastd.X - curd.X;
@@ -101,27 +128,38 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
}
const ulastfirst = u[last - first];
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
u[i - first] /= ulastfirst;
}
return u;
}
/*
-* B0, B1, B2, B3 :
-* Bezier multipliers
-*/
-function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
-function B3(u: number) { return u * u * u; }
+ * B0, B1, B2, B3 :
+ * Bezier multipliers
+ */
+function B0(u: number) {
+ const tmp = 1.0 - u;
+ return tmp * tmp * tmp;
+}
+function B1(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * tmp * tmp;
+}
+function B2(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * u * tmp;
+}
+function B3(u: number) {
+ return u * u * u;
+}
function bounds(p: Point[]) {
const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
- if (r.minx > r.maxx) (r.minx, r.maxx);
+ if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
- for (var i = 1; i < 3; i++) {
+ for (let i = 1; i < 3; i++) {
if (p[i].X < r.minx) r.minx = p[i].X;
else if (p[i].X > r.maxx) r.maxx = p[i].X;
@@ -131,37 +169,36 @@ function bounds(p: Point[]) {
return r;
}
-
function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
const sz = 4;
const Vtemp = new Array<Array<Point>>(4);
- for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
+ for (let i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
/* Copy control points */
// std::copy(p.begin(), p.end(), Vtemp[0]);
- for (var i = 0; i < sz; i++) {
+ for (let i = 0; i < sz; i++) {
Vtemp[0][i].X = p[i].X;
Vtemp[0][i].Y = p[i].Y;
}
/* Triangle computation */
- for (var i = 1; i < sz; i++) {
- for (var j = 0; j < sz - i; j++) {
+ for (let i = 1; i < sz; i++) {
+ for (let j = 0; j < sz - i; j++) {
const a = Vtemp[i - 1][j];
const b = Vtemp[i - 1][j + 1];
Vtemp[i][j].X = b.X * t + a.X * (1 - t);
- Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
+ Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
}
}
if (left) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
left[j].X = Vtemp[j][0].X;
left[j].Y = Vtemp[j][0].Y;
}
}
if (right) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
right[j].X = Vtemp[sz - 1 - j][j].X;
right[j].Y = Vtemp[sz - 1 - j][j].Y;
}
@@ -169,51 +206,54 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
}
/*
-* Recursively intersect two curves keeping track of their real parameters
-* and depths of intersection.
-* The results are returned in a 2-D array of doubles indicating the parameters
-* for which intersections are found. The parameters are in the order the
-* intersections were found, which is probably not in sorted order.
-* When an intersection is found, the parameter value for each of the two
-* is stored in the index elements array, and the index is incremented.
-*
-* If either of the curves has subdivisions left before it is straight
-* (depth > 0)
-* that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
-* the depth(s) is (are) decremented, and the parameter value(s) corresponding
-* to the midpoints(s) is (are) computed.
-* Then each of the subcurves of one curve is intersected with each of the
-* subcurves of the other curve, first by testing the bounding boxes for
-* interference. If there is any bounding box interference, the corresponding
-* subcurves are recursively intersected.
-*
-* If neither curve has subdivisions left, the line segments from the first
-* to last control point of each segment are intersected. (Actually the
-* only the parameter value corresponding to the intersection point is found).
-*
-* The apriori flatness test is probably more efficient than testing at each
-* level of recursion, although a test after three or four levels would
-* probably be worthwhile, since many curves become flat faster than their
-* asymptotic rate for the first few levels of recursion.
-*
-* The bounding box test fails much more frequently than it succeeds, providing
-* substantial pruning of the search space.
-*
-* Each (sub)curve is subdivided only once, hence it is not possible that for
-* one final line intersection test the subdivision was at one level, while
-* for another final line intersection test the subdivision (of the same curve)
-* was at another. Since the line segments share endpoints, the intersection
-* is robust: a near-tangential intersection will yield zero or two
-* intersections.
-*/
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found. The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference. If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected. (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another. Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
if (deptha > 0) {
- const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+ const a1 = new Array<Point>(4);
+ const a2 = new Array<Point>(4);
splitCubic(a, 0.5, a1, a2);
const tmid = (t0 + t1) * 0.5;
deptha--;
if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
splitCubic(b, 0.5, b1, b2);
const umid = (u0 + u1) * 0.5;
depthb--;
@@ -229,8 +269,7 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
if (SmartRect.Intersect(bounds(a2), bounds(b2))) {
recursively_intersect(a2, tmid, t1, deptha, b2, umid, u1, depthb, parameters);
}
- }
- else {
+ } else {
if (SmartRect.Intersect(bounds(a1), bounds(b))) {
recursively_intersect(a1, t0, tmid, deptha, b, u0, u1, depthb, parameters);
}
@@ -238,50 +277,45 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
recursively_intersect(a2, tmid, t1, deptha, b, u0, u1, depthb, parameters);
}
}
- }
+ } else if (depthb > 0) {
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
+ splitCubic(b, 0.5, b1, b2);
+ const umid = (u0 + u1) * 0.5;
+ depthb--;
+ if (SmartRect.Intersect(bounds(a), bounds(b1))) {
+ recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
+ }
+ if (SmartRect.Intersect(bounds(a), bounds(b2))) {
+ recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
+ }
+ } // Both segments are fully subdivided; now do line segments
else {
- if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
- splitCubic(b, 0.5, b1, b2);
- const umid = (u0 + u1) * 0.5;
- depthb--;
- if (SmartRect.Intersect(bounds(a), bounds(b1))) {
- recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
- }
- if (SmartRect.Intersect(bounds(a), bounds(b2))) {
- recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
- }
+ const xlk = a[3].X - a[0].X;
+ const ylk = a[3].Y - a[0].Y;
+ const xnm = b[3].X - b[0].X;
+ const ynm = b[3].Y - b[0].Y;
+ const xmk = b[0].X - a[0].X;
+ const ymk = b[0].Y - a[0].Y;
+ const det = xnm * ylk - ynm * xlk;
+ if (1.0 + det === 1.0) {
+ return;
}
- else // Both segments are fully subdivided; now do line segments
- {
- const xlk = a[3].X - a[0].X;
- const ylk = a[3].Y - a[0].Y;
- const xnm = b[3].X - b[0].X;
- const ynm = b[3].Y - b[0].Y;
- const xmk = b[0].X - a[0].X;
- const ymk = b[0].Y - a[0].Y;
- const det = xnm * ylk - ynm * xlk;
- if (1.0 + det === 1.0) {
- return;
- }
- else {
- const detinv = 1.0 / det;
- const s = (xnm * ymk - ynm * xmk) * detinv;
- const t = (xlk * ymk - ylk * xmk) * detinv;
- if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
- return;
- }
- parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
- }
+ const detinv = 1.0 / det;
+ const s = (xnm * ymk - ynm * xmk) * detinv;
+ const t = (xlk * ymk - ylk * xmk) * detinv;
+ if (s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0 || isNaN(s) || isNaN(t)) {
+ return;
}
+ parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
}
}
/*
-* EvalBezier :
-* Evaluate a Bezier curve at a particular parameter value
-*
-*/
+ * EvalBezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ *
+ */
const MAX_DEGREE = 5;
function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
if (degree + 1 > MAX_DEGREE) {
@@ -293,35 +327,36 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
/* Copy array */
- for (var i = 0; i <= degree; i++) {
+ for (let i = 0; i <= degree; i++) {
Vtemp[i].X = V[i].X;
Vtemp[i].Y = V[i].Y;
}
/* Triangle computation */
- for (var i = 1; i <= degree; i++) {
- for (var j = 0; j <= degree - i; j++) {
+ for (let i = 1; i <= degree; i++) {
+ for (let j = 0; j <= degree - i; j++) {
Vtemp[j].X = (1.0 - t) * Vtemp[j].X + t * Vtemp[j + 1].X;
Vtemp[j].Y = (1.0 - t) * Vtemp[j].Y + t * Vtemp[j + 1].Y;
}
}
result[0] = Vtemp[0].X;
- result[1] = Vtemp[0].Y;// Point on curve at parameter t
+ result[1] = Vtemp[0].Y; // Point on curve at parameter t
}
function EvalBezierFast(p: Point[], t: number, result: number[]) {
const n = 3;
const u = 1.0 - t;
- var bc = 1, tn = 1;
- var tmpX = p[0].X * u;
- var tmpY = p[0].Y * u;
- tn = tn * t;
- bc = bc * (n - 1 + 1) / 1;
+ let bc = 1;
+ let tn = 1;
+ let tmpX = p[0].X * u;
+ let tmpY = p[0].Y * u;
+ tn *= t;
+ bc = (bc * (n - 1 + 1)) / 1;
tmpX = (tmpX + tn * bc * p[1].X) * u;
tmpY = (tmpY + tn * bc * p[1].Y) * u;
- tn = tn * t;
- bc = bc * (n - 2 + 1) / 2;
+ tn *= t;
+ bc = (bc * (n - 2 + 1)) / 2;
tmpX = (tmpX + tn * bc * p[2].X) * u;
tmpY = (tmpY + tn * bc * p[2].Y) * u;
@@ -329,9 +364,9 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
result[1] = tmpY + tn * t * p[3].Y;
}
/*
-* ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
-*Approximate unit tangents at endpoints and "center" of digitized curve
-*/
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ *Approximate unit tangents at endpoints and "center" of digitized curve
+ */
function ComputeLeftTangent(d: Point[], end: number) {
const use = 1;
const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
@@ -348,19 +383,19 @@ function ComputeCenterTangent(d: Point[], center: number) {
}
const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
- var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
+ let tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
if (tHatCenter === new Point(0, 0)) {
- tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
+ tHatCenter = new Point(-V1.Y, -V1.X); // V1.Perp();
}
return Normalize(tHatCenter);
}
function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
const nPts = last - first + 1; // Number of pts in sub-curve
- const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
- const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ax = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ay = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
/* Compute the A's */
- for (var i = 0; i < nPts; i++) {
+ for (let i = 0; i < nPts; i++) {
const uprime = uPrime[i];
const b1 = B1(uprime);
const b2 = B2(uprime);
@@ -371,39 +406,42 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
}
/* Create the C and X matrices */
- const C = [[0, 0], [0, 0]];
+ const C = [
+ [0, 0],
+ [0, 0],
+ ];
const df = d[first];
const dl = d[last];
- const X = [0, 0]; // Matrix X
- for (var i = 0; i < nPts; i++) {
- C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; //A[i+0*nPts].Dot(A[i+0*nPts]);
- C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
+ const X = [0, 0]; // Matrix X
+ for (let i = 0; i < nPts; i++) {
+ C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; // A[i+0*nPts].Dot(A[i+0*nPts]);
+ C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts]; // A[i+0*nPts].Dot(A[i+1*nPts]);
C[1][0] = C[0][1];
- C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
+ C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts]; // A[i+1*nPts].Dot(A[i+1*nPts]);
const uprime = uPrime[i];
const b0plb1 = B0(uprime) + B1(uprime);
const b2plb3 = B2(uprime) + B3(uprime);
const df1 = d[first + i];
- const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
- const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+ const tmpX = df1.X - (df.X * b0plb1 + dl.X * b2plb3);
+ const tmpY = df1.Y - (df.Y * b0plb1 + dl.Y * b2plb3);
X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
- X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
+ X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; // A[i+1*nPts].Dot(tmp)
}
/* Compute the determinants of C and X */
- const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+ const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1] || C[0][0] * C[1][1] * 10e-12;
const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
/* Finally, derive alpha values */
- var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
- var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
+ let alpha_l = det_C0_C1 === 0 ? 0.0 : det_X_C1 / det_C0_C1;
+ let alpha_r = det_C0_C1 === 0 ? 0.0 : det_C0_X / det_C0_C1;
/* If alpha negative, use the Wu/Barsky heuristic (see text) */
/* (if alpha is 0, you get coincident control points that lead to
- * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+ * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
const epsilon = 1.0e-6 * segLength;
if (alpha_l < epsilon || alpha_r < epsilon) {
@@ -415,10 +453,10 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
/* positioned exactly at the first and last data points */
/* Control points 1 and 2 are positioned an alpha distance out */
/* on the tangent vectors, left and right, respectively */
- result[0] = df;// RETURN bezier curve ctl pts
+ result[0] = df; // RETURN bezier curve ctl pts
result[3] = dl;
- result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
- result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
+ result[1] = new Point(df.X + tHat1.X * alpha_l, df.Y + tHat1.Y * alpha_l);
+ result[2] = new Point(dl.X + tHat2.X * alpha_r, dl.Y + tHat2.Y * alpha_r);
}
/*
@@ -426,21 +464,24 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
* Use Newton-Raphson iteration to find better root.
*/
function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
- const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
- const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+ const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+ const Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
+ const Q_u = [0, 0];
+ const Q1_u = [0, 0];
+ const Q2_u = [0, 0]; // u evaluated at Q, Q', & Q''
/* Compute Q(u) */
- var uPrime: number; // Improved u
+ let uPrime: number; // Improved u
EvalBezierFast(Q, u, Q_u);
/* Generate control vertices for Q' */
- for (var i = 0; i <= 2; i++) {
+ for (let i = 0; i <= 2; i++) {
Q1[i].X = (Q[i + 1].X - Q[i].X) * 3.0;
Q1[i].Y = (Q[i + 1].Y - Q[i].Y) * 3.0;
}
/* Generate control vertices for Q'' */
- for (var i = 0; i <= 1; i++) {
+ for (let i = 0; i <= 1; i++) {
Q2[i].X = (Q1[i + 1].X - Q1[i].X) * 2.0;
Q2[i].Y = (Q1[i + 1].Y - Q1[i].Y) * 2.0;
}
@@ -450,20 +491,20 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
EvalBezier(Q2, 1, u, Q2_u);
/* Compute f(u)/f'(u) */
- const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
- const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+ const numerator = (Q_u[0] - P.X) * Q1_u[0] + (Q_u[1] - P.Y) * Q1_u[1];
+ const denominator = Q1_u[0] * Q1_u[0] + Q1_u[1] * Q1_u[1] + (Q_u[0] - P.X) * Q2_u[0] + (Q_u[1] - P.Y) * Q2_u[1];
if (denominator === 0.0) {
uPrime = u;
- } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+ } else uPrime = u - numerator / denominator; /* u = u - f(u)/f'(u) */
return uPrime;
}
function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
- const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
- const maxIterations = 4; // Max times to try iterating
+ const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
+ const maxIterations = 4; // Max times to try iterating
const iterationError = error * error; // Error below which you try iterating
- const nPts = last - first + 1; // Number of points in subset
+ const nPts = last - first + 1; // Number of points in subset
/* Use heuristic if region only has two points in it */
if (nPts === 2) {
@@ -471,8 +512,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
bezCurve[0] = d[first];
bezCurve[3] = d[last];
- bezCurve[1] = new Point(bezCurve[0].X + (tHat1.X * dist), bezCurve[0].Y + (tHat1.Y * dist));
- bezCurve[2] = new Point(bezCurve[3].X + (tHat2.X * dist), bezCurve[3].Y + (tHat2.Y * dist));
+ bezCurve[1] = new Point(bezCurve[0].X + tHat1.X * dist, bezCurve[0].Y + tHat1.Y * dist);
+ bezCurve[2] = new Point(bezCurve[3].X + tHat2.X * dist, bezCurve[3].Y + tHat2.Y * dist);
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -481,11 +522,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
}
/* Parameterize points, and attempt to fit curve */
- var u = ChordLengthParameterize(d, first, last);
+ let u = ChordLengthParameterize(d, first, last);
GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
/* Find max deviation of points to fitted curve */
- const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
+ const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
if (maxError < Math.abs(error)) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -496,11 +537,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
/* If error not too large, try some reparameterization */
/* and iteration */
if (maxError < iterationError) {
- for (var i = 0; i < maxIterations; i++) {
- const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
+ for (let i = 0; i < maxIterations; i++) {
+ const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
- const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
- if (maxError < error) {
+ const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+ if (maximumError < error) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
result.push(bezCurve[3]);
@@ -527,13 +568,13 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
tHat1 = tHat1 ?? Normalize(ComputeLeftTangent(d, 0));
tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
tHat2 = new Point(-tHat2.X, -tHat2.Y);
- var u = ChordLengthParameterize(d, 0, d.length - 1);
+ let u = ChordLengthParameterize(d, 0, d.length - 1);
const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
- GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
- var finalCtrls = bezCurveCtrls.slice();
- var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
- for (var i = 0; i < 10; i++) {
- const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
+ GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
+ let finalCtrls = bezCurveCtrls.slice();
+ let { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
+ for (let i = 0; i < 10; i++) {
+ const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
if (maxError < error) {
@@ -1428,4 +1469,4 @@ spliceR[0] += v;
}
#endif
-*/ \ No newline at end of file
+*/
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 0c49aeed4..2224e642d 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,33 +1,39 @@
-import * as React from 'react';
-import * as uuid from 'uuid';
-import '.././SettingsManager.scss';
-import './ReportManager.scss';
-import ReactLoading from 'react-loading';
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/media-has-caption */
+/* eslint-disable react/no-unused-class-component-methods */
+import { Octokit } from '@octokit/core';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
-import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
import { HiOutlineArrowLeft } from 'react-icons/hi';
-import { Issue } from './reportManagerSchema';
-import { observer } from 'mobx-react';
+import { MdRefresh } from 'react-icons/md';
+import ReactLoading from 'react-loading';
+import * as uuid from 'uuid';
+import { ClientUtils } from '../../../ClientUtils';
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 { MainViewModal } from '../../views/MainViewModal';
+import '../SettingsManager.scss';
import { SettingsManager } from '../SettingsManager';
+import './ReportManager.scss';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
+import { Issue } from './reportManagerSchema';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
/**
* Class for reporting and viewing Github issues within the app.
*/
@observer
export class ReportManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ReportManager;
@observable private isOpen = false;
@observable private query = '';
+ // eslint-disable-next-line react/sort-comp
@action private setQuery = (q: string) => {
this.query = q;
};
@@ -82,7 +88,9 @@ export class ReportManager extends React.Component<{}> {
this.formData = newData;
});
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action(async () => {
this.isOpen = true;
if (this.shownIssues.length === 0) {
@@ -133,7 +141,7 @@ export class ReportManager extends React.Component<{}> {
const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
owner: 'brown-dash',
repo: 'Dash-Web',
- title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+ title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()),
body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
labels: ['from-dash-app', this.formData.type, this.formData.priority],
});
@@ -164,7 +172,7 @@ export class ReportManager extends React.Component<{}> {
* @returns JSX element of a piece of media (image, video, audio)
*/
private getMediaPreview = (fileData: FileData): JSX.Element => {
- const file = fileData.file;
+ const { file } = fileData;
const mimeType = file.type;
const preview = URL.createObjectURL(file);
@@ -179,7 +187,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('video/')) {
+ }
+ if (mimeType.startsWith('video/')) {
return (
<div key={fileData._id} className="report-media-wrapper">
<div className="report-media-content">
@@ -193,7 +202,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('audio/')) {
+ }
+ if (mimeType.startsWith('audio/')) {
return (
<div key={fileData._id} className="report-audio-wrapper">
<audio src={preview} controls />
@@ -203,7 +213,7 @@ export class ReportManager extends React.Component<{}> {
</div>
);
}
- return <></>;
+ return <div />;
};
/**
@@ -306,8 +316,8 @@ export class ReportManager extends React.Component<{}> {
<div className="report-selects">
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Type'}
- closeOnSelect={true}
+ formLabel="Type"
+ closeOnSelect
items={bugDropdownItems}
selectedVal={this.formData.type}
setSelectedVal={val => {
@@ -319,8 +329,8 @@ export class ReportManager extends React.Component<{}> {
/>
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Priority'}
- closeOnSelect={true}
+ formLabel="Priority"
+ closeOnSelect
items={priorityDropdownItems}
selectedVal={this.formData.priority}
setSelectedVal={val => {
@@ -346,7 +356,7 @@ export class ReportManager extends React.Component<{}> {
text="Submit"
type={Type.TERT}
color={StrCast(Doc.UserDoc().userVariantColor)}
- icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
iconPlacement="right"
onClick={() => {
this.reportIssue();
@@ -363,7 +373,7 @@ export class ReportManager extends React.Component<{}> {
/>
)}
<div style={{ position: 'absolute', top: '4px', right: '4px' }}>
- <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
</div>
</div>
);
@@ -375,9 +385,8 @@ export class ReportManager extends React.Component<{}> {
private reportComponent = () => {
if (this.viewState === ViewState.VIEW) {
return this.viewIssuesComponent();
- } else {
- return this.reportIssueComponent();
}
+ return this.reportIssueComponent();
};
render() {
@@ -385,7 +394,7 @@ export class ReportManager extends React.Component<{}> {
<MainViewModal
contents={this.reportComponent()}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
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
index 1e226bf6d..cecebc648 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,9 +1,15 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
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 { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import { Issue } from './reportManagerSchema';
import { StrCast } from '../../../fields/Types';
import { Doc } from '../../../fields/Doc';
@@ -18,7 +24,7 @@ interface FilterProps<T> {
}
// filter ui for issues (horizontal list of tags)
-export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) {
// establishing theme
const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
const colors = darkMode ? darkColors : lightColors;
@@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
return (
<div className="issues-filter">
<Tag
- text={'All'}
+ text="All"
onClick={() => {
setActiveValue(null);
}}
@@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
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}
- />
- );
- })}
+ {items.map(item => (
+ <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;
@@ -64,7 +68,7 @@ interface IssueCardProps {
}
// Component for the issue cards list on the left
-export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+export function IssueCard({ issue, onSelect }: IssueCardProps) {
const [textColor, setTextColor] = React.useState('');
const [bgColor, setBgColor] = React.useState('transparent');
const [borderColor, setBorderColor] = React.useState('transparent');
@@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
<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) => {
+export function IssueView({ issue }: IssueViewProps) {
const [issueBody, setIssueBody] = React.useState('');
// Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
@@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => {
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)) {
+ }
+ if (fileRegex.test(part)) {
const tag = await parseDashFiles(part);
return tag;
- } else if (localRegex.test(part)) {
+ }
+ if (localRegex.test(part)) {
const tag = await parseLocalFiles(part);
return tag;
- } else {
- return part;
}
+ return part;
})
);
@@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
};
// 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 parseFileTag = async (tag: string): Promise<string | undefined> => {
const regex = /src="([^"]+)"/;
let url = '';
const match = tag.match(regex);
@@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => {
case '.png':
case '.jpeg':
case '.gif':
- return await getDisplayedFile(url, 'image');
+ return getDisplayedFile(url, 'image');
// video
case '.mp4':
case '.mpeg':
case '.webm':
case '.mov':
- return await getDisplayedFile(url, 'video');
- //audio
+ return getDisplayedFile(url, 'video');
+ // audio
case '.mp3':
case '.wav':
case '.ogg':
- return await getDisplayedFile(url, 'audio');
+ return getDisplayedFile(url, 'audio');
+ default:
}
return tag;
};
@@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => {
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;
+ return getDisplayedFile(url, 'image');
}
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
+ }
+ return url;
};
// Returns the corresponding HTML tag for a src url
@@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => {
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;
+ return getDisplayedFile(url, 'image');
+ }
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
}
+ return url;
};
- const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+ const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => {
switch (fileType) {
- case 'image':
+ 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':
+ }
+ 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':
+ }
+ 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`;
+ }
+ default:
}
+ return undefined;
};
// Loads an image and returns a promise that resolves as whether the image is valid or not
@@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<div className="issue-view">
<span className="issue-label">
Issue{' '}
- <a className="issue-link" href={issue.html_url} target="_blank">
+ <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer">
#{issue.number}
</a>
</span>
@@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
</div>
);
-};
+}
interface TagProps {
text: string;
@@ -305,7 +318,7 @@ interface TagProps {
}
// Small tag for labels of the issue
-export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) {
return (
<div
onClick={onClick ?? (() => {})}
@@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo
{text}
</div>
);
-};
+}
interface FormInputProps {
value: string;
placeholder: string;
onChange: (val: string) => void;
}
-export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormInput({ value, placeholder, onChange }: FormInputProps) {
const [inputBorderColor, setInputBorderColor] = React.useState('');
return (
@@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
}}
/>
);
-};
+}
-export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormTextArea({ value, placeholder, onChange }: FormInputProps) {
const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
return (
@@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) =
}}
/>
);
-};
+}
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 9a1c7c3e9..171c24393 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
/**
* Issue interface schema from Github.
*/
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 22e5eebbb..f14967e0a 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
// 200 status means success
if (res.status === 200) {
return res.data;
- } else {
- throw new Error('Error getting issues');
}
+ throw new Error('Error getting issues');
};
/**
@@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => {
* @param link response from file upload
* @returns server file path
*/
-export const getServerPath = (link: any): string => {
- return link.result.accessPaths.agnostic.server as string;
-};
+export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string;
/**
* Uploads media files to the server.
@@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin
alert(err);
}
}
+ return undefined;
};
// helper functions
@@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug
passesPriority = issue.labels.some(label => {
if (typeof label === 'string') {
return label === priorityFilter;
- } else {
- return label.name === priorityFilter;
}
+ return label.name === priorityFilter;
});
}
if (bugFilter) {
passesBug = issue.labels.some(label => {
if (typeof label === 'string') {
return label === bugFilter;
- } else {
- return label.name === bugFilter;
}
+ return label.name === bugFilter;
});
}
return passesPriority && passesBug;
@@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = {
export const getLabelColors = (label: string): string[] => {
if (prioritySet.has(label as Priority)) {
return priorityColors[label];
- } else if (bugSet.has(label as BugType)) {
+ }
+ if (bugSet.has(label as BugType)) {
return bugColors[label];
}
return ['#0f73f6', '#ffffff'];
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 57e8516ac..0f98a2710 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -14,19 +14,17 @@ const imageSize = require('image-size');
const HttpError = require('standard-http-error');
module.exports = function requestImageSize(options: any) {
- let opts = {
+ let opts: any = {
encoding: null,
};
if (options && typeof options === 'object') {
opts = Object.assign(options, opts);
} else if (options && typeof options === 'string') {
- opts = Object.assign(
- {
- uri: options,
- },
- opts
- );
+ opts = {
+ uri: options,
+ ...opts,
+ };
} else {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) {
req.on('response', (res: any) => {
if (res.statusCode >= 400) {
- return reject(new HttpError(res.statusCode, res.statusMessage));
+ reject(new HttpError(res.statusCode, res.statusMessage));
+ return;
}
let buffer = Buffer.from([]);
@@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) {
size = imageSize(buffer);
if (size) {
resolve(size);
- return req.abort();
+ req.abort();
}
- } catch (err) {}
+ } catch (err) {
+ /* empty */
+ }
});
res.on('error', reject);
res.on('end', () => {
if (!size) {
- return reject(new Error('Image has no size'));
+ reject(new Error('Image has no size'));
+ return;
}
size.downloaded = buffer.length;
- return resolve(size);
+ resolve(size);
});
});
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index db7e64deb..b1eb730fa 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -3,6 +3,7 @@ import * as React from 'react';
import { SettingsManager } from '../util/SettingsManager';
import './AntimodeMenu.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
+
export interface AntimodeMenuProps {}
/**
@@ -76,7 +77,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected pointerLeave = (e: React.PointerEvent) => {
+ protected pointerLeave = () => {
if (!this.Pinned && this._canFade) {
this._transitionProperty = 'opacity';
this._transitionDuration = '0.5s';
@@ -87,7 +88,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected pointerEntered = (e: React.PointerEvent) => {
+ protected pointerEntered = () => {
this._transitionProperty = 'opacity';
this._transitionDuration = '0.1s';
this._transitionDelay = '';
@@ -95,8 +96,10 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected togglePin = (e: React.MouseEvent) => {
- runInAction(() => (this.Pinned = !this.Pinned));
+ protected togglePin = () => {
+ runInAction(() => {
+ this.Pinned = !this.Pinned;
+ });
};
protected dragStart = (e: React.PointerEvent) => {
@@ -114,8 +117,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
@action
protected dragging = (e: PointerEvent) => {
- const width = this._mainCont.current!.getBoundingClientRect().width;
- const height = this._mainCont.current!.getBoundingClientRect().height;
+ const { width, height } = this._mainCont.current!.getBoundingClientRect();
const left = e.pageX - this._offsetX;
const top = e.pageY - this._offsetY;
@@ -139,9 +141,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
e.preventDefault();
};
- protected getDragger = () => {
- return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
- };
+ protected getDragger = () => <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
protected getElement(buttons: JSX.Element, expanded: boolean = false) {
const containerClass = expanded ? 'antimodeMenu-cont expanded' : 'antimodeMenu-cont';
diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
index ca4e5f2bc..64b8a8446 100644
--- a/src/client/views/ComponentDecorations.tsx
+++ b/src/client/views/ComponentDecorations.tsx
@@ -1,10 +1,11 @@
import { observer } from 'mobx-react';
+import * as React from 'react';
import { SelectionManager } from '../util/SelectionManager';
import './ComponentDecorations.scss';
-import * as React from 'react';
@observer
export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> {
+ // eslint-disable-next-line no-use-before-define
static Instance: ComponentDecorations;
render() {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index ca877b93e..d784a14b8 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,16 +1,19 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable default-param-last */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StrCast } from '../../fields/Types';
-import { SettingsManager } from '../util/SettingsManager';
+import { DivHeight, DivWidth } from '../../ClientUtils';
+import { SnappingManager } from '../util/SnappingManager';
import './ContextMenu.scss';
import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { DivHeight, DivWidth } from '../../Utils';
@observer
export class ContextMenu extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: ContextMenu;
private _ignoreUp = false;
@@ -124,8 +127,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@action
displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
- //maxX and maxY will change if the UI/font size changes, but will work for any amount
- //of items added to the menu
+ // maxX and maxY will change if the UI/font size changes, but will work for any amount
+ // of items added to the menu
this._showSearch = showSearch;
this._pageX = x;
@@ -147,15 +150,13 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@computed get filteredItems(): (OriginalMenuProps | string[])[] {
const searchString = this._searchString.toLowerCase().split(' ');
- const matches = (descriptions: string[]): boolean => {
- return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
- };
+ const matches = (descriptions: string[]) => searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
const flattenItems = (items: ContextMenuProps[], groupFunc: (groupName: any) => string[]) => {
let eles: (OriginalMenuProps | string[])[] = [];
const leaves: OriginalMenuProps[] = [];
- for (const item of items) {
- const description = item.description;
+ items.forEach(item => {
+ const { description } = item;
const path = groupFunc(description);
if ('subitems' in item) {
const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
@@ -163,13 +164,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
eles.push(path);
eles = eles.concat(children);
}
- } else {
- if (!matches(path)) {
- continue;
- }
+ } else if (matches(path)) {
leaves.push(item);
}
- }
+ });
eles = [...leaves, ...eles];
@@ -192,7 +190,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
key={index + value.join(' -> ')}
className="contextMenu-group"
style={{
- background: StrCast(SettingsManager.userVariantColor),
+ background: SnappingManager.userVariantColor,
}}>
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
@@ -224,11 +222,11 @@ export class ContextMenu extends ObservableReactComponent<{}> {
display: this._display ? '' : 'none',
left: this.pageX,
...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ color: SnappingManager.userColor,
}}>
{!this.itemsNeedSearch ? null : (
- <span className={'search-icon'}>
+ <span className="search-icon">
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
@@ -241,6 +239,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
value={this._searchString}
onKeyDown={this.onKeyDown}
onChange={this.onChange}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
/>
</span>
@@ -267,7 +266,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
if (item) {
item.event({ x: this.pageX, y: this.pageY });
} else {
- //if (this._searchString.startsWith(this._defaultPrefix)) {
+ // if (this._searchString.startsWith(this._defaultPrefix)) {
this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
}
this.closeMenu();
@@ -281,12 +280,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
this._searchString = e.target.value;
if (!this._searchString) {
this._selectedIndex = -1;
+ } else if (this._selectedIndex === -1) {
+ this._selectedIndex = 0;
} else {
- if (this._selectedIndex === -1) {
- this._selectedIndex = 0;
- } else {
- this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
- }
+ this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
}
};
}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index d15ab749c..eb1030eec 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,26 +1,28 @@
-import * as React from 'react';
-import { observable, action, runInAction, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
+/* eslint-disable react/jsx-props-no-spreading */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
-import { SettingsManager } from '../util/SettingsManager';
import { ObservableReactComponent } from './ObservableReactComponent';
export interface OriginalMenuProps {
description: string;
event: (stuff?: any) => void;
undoable?: boolean;
- icon: IconProp | JSX.Element; //maybe should be optional (icon?)
+ icon: IconProp | JSX.Element; // maybe should be optional (icon?)
closeMenu?: () => void;
}
export interface SubmenuProps {
description: string;
+ // eslint-disable-next-line no-use-before-define
subitems: ContextMenuProps[];
noexpand?: boolean;
addDivider?: boolean;
- icon: IconProp; //maybe should be optional (icon?)
+ icon: IconProp; // maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -37,7 +39,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
}
componentDidMount() {
- runInAction(() => (this._items.length = 0));
+ runInAction(() => {
+ this._items.length = 0;
+ });
if ((this._props as SubmenuProps)?.subitems) {
(this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
}
@@ -67,7 +71,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
this._overPosY = e.clientY;
this._overPosX = e.clientX;
this.currentTimeout = setTimeout(
- action(() => (this.overItem = true)),
+ action(() => {
+ this.overItem = true;
+ }),
ContextMenuItem.timeout
);
};
@@ -81,7 +87,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
return;
}
this.currentTimeout = setTimeout(
- action(() => (this.overItem = false)),
+ action(() => {
+ this.overItem = false;
+ }),
ContextMenuItem.timeout
);
};
@@ -97,14 +105,15 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
{this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
<div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
<div
- className={`contextMenu-item-background`}
+ className="contextMenu-item-background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
}}
/>
</div>
);
- } else if ('subitems' in this._props) {
+ }
+ if ('subitems' in this._props) {
const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
@@ -115,7 +124,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
style={{
marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%',
marginTop,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
}}>
{this._items.map(prop => (
<ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
@@ -144,17 +153,18 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
) : null}
<div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
{this._props.description}
- <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
+ <FontAwesomeIcon icon="angle-right" size="lg" style={{ position: 'absolute', right: '10px' }} />
</div>
<div
- className={`contextMenu-item-background`}
+ className="contextMenu-item-background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
}}
/>
{submenu}
</div>
);
}
+ return null;
}
}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index a4f598d1a..25415a4f0 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaPlus } from 'react-icons/fa';
+import { ClientUtils } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -14,21 +17,19 @@ import { ScriptField } from '../../fields/ScriptField';
import { Cast, ImageCast, StrCast } from '../../fields/Types';
import { normalizeEmail } from '../../fields/util';
import { DocServer } from '../DocServer';
-import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
+import { dropActionType } from '../util/DropActionTypes';
import { HistoryUtil } from '../util/History';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionView } from './collections/CollectionView';
+import { SnappingManager } from '../util/SnappingManager';
+import { undoBatch, undoable } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import './DashboardView.scss';
-import { Colors } from './global/globalEnums';
import { MainViewModal } from './MainViewModal';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { dropActionType } from '../util/DragManager';
+import { Colors } from './global/globalEnums';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
enum DashboardGroup {
MyDashboards,
@@ -49,10 +50,18 @@ export class DashboardView extends ObservableReactComponent<{}> {
@observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
@observable private newDashboardName = '';
@observable private newDashboardColor = '#AFAFAF';
- @action abortCreateNewDashboard = () => (this.openModal = false);
- @action setNewDashboardName = (name: string) => (this.newDashboardName = name);
- @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color);
- @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group);
+ @action abortCreateNewDashboard = () => {
+ this.openModal = false;
+ };
+ @action setNewDashboardName = (name: string) => {
+ this.newDashboardName = name;
+ };
+ @action setNewDashboardColor = (color: string) => {
+ this.newDashboardColor = color;
+ };
+ @action selectDashboardGroup = (group: DashboardGroup) => {
+ this.selectedDashboardGroup = group;
+ };
clickDashboard = (e: React.MouseEvent, dashboard: Doc) => {
if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) {
@@ -65,7 +74,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
getDashboards = (whichGroup: DashboardGroup) => {
if (whichGroup === DashboardGroup.MyDashboards) {
- return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail);
+ return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail());
}
return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
};
@@ -84,11 +93,11 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="new-dashboard"
style={{
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ color: SnappingManager.userColor,
}}>
<div className="header">Create New Dashboard</div>
- <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SettingsManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
+ <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SnappingManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
<ColorPicker
formLabel="Background" //
colorPickerType="github"
@@ -98,8 +107,8 @@ export class DashboardView extends ObservableReactComponent<{}> {
setSelectedColor={this.setNewDashboardColor}
/>
<div className="button-bar">
- <Button text="Cancel" color={SettingsManager.userColor} onClick={this.abortCreateNewDashboard} />
- <Button text="Create" color={SettingsManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
+ <Button text="Cancel" color={SnappingManager.userColor} onClick={this.abortCreateNewDashboard} />
+ <Button text="Create" color={SnappingManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
</div>
</div>
);
@@ -133,15 +142,15 @@ export class DashboardView extends ObservableReactComponent<{}> {
};
render() {
- const color = SettingsManager.userColor;
- const variant = SettingsManager.userVariantColor;
+ const color = SnappingManager.userColor;
+ const variant = SnappingManager.userVariantColor;
return (
<>
<div className="dashboard-view">
<div className="left-menu">
- <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
+ <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 + ')'}
+ 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"
@@ -156,7 +165,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
: this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
const href = ImageCast(dashboard.thumb)?.url?.href;
const shared = Object.keys(dashboard[DocAcl])
- .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
+ .filter(key => key !== `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}` && !['acl-Me', 'acl-Guest'].includes(key))
.some(key => dashboard[DocAcl][key] !== AclPrivate);
return (
<div
@@ -166,14 +175,22 @@ export class DashboardView extends ObservableReactComponent<{}> {
onContextMenu={e => this.onContextMenu(dashboard, e)}
onClick={e => this.clickDashboard(e, dashboard)}>
<img
+ alt=""
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">
- <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (dashboard[DocData].title = val)} />
- {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
+ <EditableText
+ type={Type.PRIM}
+ color={color}
+ val={StrCast(dashboard.title)}
+ setVal={val => {
+ dashboard[DocData].title = val;
+ }}
+ />
+ {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div />}
<div
className="more"
onPointerDown={e => {
@@ -187,7 +204,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -201,7 +218,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -209,7 +226,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
)}
</div>
</div>
- <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive={true} closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
+ <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
</>
);
}
@@ -217,9 +234,6 @@ export class DashboardView extends ObservableReactComponent<{}> {
public static closeActiveDashboard() {
Doc.ActiveDashboard = undefined;
}
- public static snapshotDashboard() {
- return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
- }
public static openSharedDashboard = (dashboard: Doc) => {
Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
@@ -250,16 +264,17 @@ export class DashboardView extends ObservableReactComponent<{}> {
});
if (state.readonly === true || state.readonly === null) {
DocServer.Control.makeReadOnly();
- } else if (state.safe) {
- if (!state.nro) {
- DocServer.Control.makeReadOnly();
- }
- CollectionView.SetSafeMode(true);
+ // } else if (state.safe) {
+ // if (!state.nro) {
+ // DocServer.Control.makeReadOnly();
+ // }
+ // CollectionView.SetSafeMode(true);
} else if (state.nro || state.nro === null || state.readonly === false) {
+ /* empty */
} else if (doc.readOnly) {
DocServer.Control.makeReadOnly();
} else {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
}
}
@@ -278,7 +293,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
public static resetDashboard = (dashboard: Doc) => {
const config = StrCast(dashboard.dockingConfig);
- const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = config.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
const components =
@@ -372,7 +387,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
title: `Untitled Tab 1`,
};
- const title = name ? name : `Dashboard ${dashboardCount}`;
+ const title = name || `Dashboard ${dashboardCount}`;
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
@@ -473,23 +488,23 @@ export class DashboardView extends ObservableReactComponent<{}> {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewDashboard() {
return DashboardView.createNewDashboard();
}, 'creates a new dashboard when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
SharingManager.Instance.open(undefined, dashboard);
}, 'opens sharing dialog for Dashboard');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function removeDashboard(dashboard: Doc) {
DashboardView.removeDashboard(dashboard);
}, 'Remove Dashboard from Dashboards');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function resetDashboard(dashboard: Doc) {
DashboardView.resetDashboard(dashboard);
}, 'move all dashboard tabs to single stack');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
DashboardView.openDashboard(Doc.MakeEmbedding(dashboard));
}, 'adds Dashboard to set of Dashboards');
-ScriptingGlobals.add(async function snapshotDashboard() {
- const batch = UndoManager.StartBatch('snapshot');
- await DashboardView.snapshotDashboard();
- batch.end();
-}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index e098bc361..b242acdba 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -7,14 +7,14 @@ import { MainViewModal } from './MainViewModal';
@observer
export class DictationOverlay extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: DictationOverlay;
@observable private _dictationState = DictationManager.placeholder;
@observable private _dictationSuccessState: boolean | undefined = undefined;
@observable private _dictationDisplayState = false;
@observable private _dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
- public isPointerDown = false;
- public overlayTimeout: NodeJS.Timeout | undefined;
+ // eslint-disable-next-line react/no-unused-class-component-methods
public hasActiveModal = false;
constructor(props: any) {
@@ -23,47 +23,32 @@ export class DictationOverlay extends React.Component {
DictationOverlay.Instance = this;
}
- public initiateDictationFade = () => {
- const duration = DictationManager.Commands.dictationFadeDuration;
- this.overlayTimeout = setTimeout(() => {
- this.dictationOverlayVisible = false;
- this.dictationSuccess = undefined;
- DictationOverlay.Instance.hasActiveModal = false;
- setTimeout(() => (this.dictatedPhrase = DictationManager.placeholder), 500);
- }, duration);
- };
- public cancelDictationFade = () => {
- if (this.overlayTimeout) {
- clearTimeout(this.overlayTimeout);
- this.overlayTimeout = undefined;
- }
- };
-
- @computed public get dictatedPhrase() {
- return this._dictationState;
- }
- @computed public get dictationSuccess() {
- return this._dictationSuccessState;
- }
- @computed public get dictationOverlayVisible() {
- return this._dictationDisplayState;
- }
- @computed public get isListening() {
- return this._dictationListeningState;
- }
-
+ @computed public get dictatedPhrase() { return this._dictationState; } // prettier-ignore
public set dictatedPhrase(value: string) {
- runInAction(() => (this._dictationState = value));
+ runInAction(() => {
+ this._dictationState = value;
+ });
}
+ @computed public get dictationSuccess() { return this._dictationSuccessState; } // prettier-ignore
public set dictationSuccess(value: boolean | undefined) {
- runInAction(() => (this._dictationSuccessState = value));
+ runInAction(() => { this._dictationSuccessState = value; }); // prettier-ignore
}
+ @computed public get dictationOverlayVisible() { return this._dictationDisplayState; } // prettier-ignore
public set dictationOverlayVisible(value: boolean) {
- runInAction(() => (this._dictationDisplayState = value));
+ runInAction(() => { this._dictationDisplayState = value; }); // prettier-ignore
}
+ @computed public get isListening() { return this._dictationListeningState; } // prettier-ignore
public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
- runInAction(() => (this._dictationListeningState = value));
+ runInAction(() => { this._dictationListeningState = value; }); // prettier-ignore
}
+ public initiateDictationFade = () => {
+ setTimeout(() => {
+ this.dictationOverlayVisible = false;
+ this.dictationSuccess = undefined;
+ DictationOverlay.Instance.hasActiveModal = false;
+ setTimeout(() => { this.dictatedPhrase = DictationManager.placeholder; }, 500); // prettier-ignore
+ }, DictationManager.Commands.dictationFadeDuration);
+ };
render() {
const success = this.dictationSuccess;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index de4df1830..fd29b1ca4 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,22 +1,52 @@
import { action, computed, makeObservable, observable } from 'mobx';
import * as React from 'react';
-import { returnFalse } from '../../Utils';
+import { returnFalse } from '../../ClientUtils';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, FieldType, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { RefField } from '../../fields/RefField';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
-import { DocUtils } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
-import { PinProps } from './nodes/trails';
+// import { DocUtils } from '../documents/Documents';
+export interface pinDataTypes {
+ scrollable?: boolean;
+ dataviz?: number[];
+ pannable?: boolean;
+ type_collection?: boolean;
+ inkable?: boolean;
+ filters?: boolean;
+ pivot?: boolean;
+ temporal?: boolean;
+ clippable?: boolean;
+ datarange?: boolean;
+ dataview?: boolean;
+ poslayoutview?: boolean;
+ dataannos?: boolean;
+ map?: boolean;
+}
+
+export interface MarqueeViewBounds {
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+}
+export interface PinProps {
+ audioRange?: boolean;
+ activeFrame?: number;
+ currentFrame?: number;
+ hidePresBox?: boolean;
+ pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
+ pinDocLayout?: boolean; // pin layout info (width/height/x/y)
+ pinAudioPlay?: boolean; // pin audio annotation
+ pinData?: pinDataTypes;
+}
/**
* Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
* Many of these methods only make sense for specific viewBox'es, but they should be written to
@@ -25,6 +55,7 @@ import { PinProps } from './nodes/trails';
export interface ViewBoxInterface {
fieldKey?: string;
annotationKey?: string;
+ promoteCollection?: () => void; // moves contents of collection to parent
updateIcon?: () => void; // updates the icon representation of the document
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;
@@ -46,7 +77,7 @@ export interface ViewBoxInterface {
IsPlaying?: () => boolean; // is a media document playing
TogglePause?: (keep?: boolean) => void; // toggle media document playing state
setFocus?: () => void; // sets input focus to the componentView
- setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+ setData?: (data: FieldType | Promise<RefField | undefined>) => boolean;
componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
@@ -218,7 +249,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean {
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
const indocs = doc instanceof Doc ? [doc] : doc;
- const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
+ const docs = indocs.filter(fdoc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(fdoc) === AclAdmin);
// docs.forEach(doc => doc.annotationOn === this.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
const targetDataDoc = this.Document[DocData]; // this.dataDoc; // we want to write to the template, not the actual data doc
@@ -227,17 +258,17 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
if (toRemove.length !== 0) {
const recentlyClosed = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
- toRemove.forEach(doc => {
- leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
- Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
- doc.embedContainer = undefined;
- if (recentlyClosed && !dontAddToRemoved && doc.type !== DocumentType.LOADING) {
- Doc.AddDocToList(recentlyClosed, 'data', doc, undefined, true, true);
- Doc.RemoveEmbedding(doc, doc);
+ toRemove.forEach(rdoc => {
+ // leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+ Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, rdoc, true);
+ rdoc.embedContainer = undefined;
+ if (recentlyClosed && !dontAddToRemoved && rdoc.type !== DocumentType.LOADING) {
+ Doc.AddDocToList(recentlyClosed, 'data', rdoc, undefined, true, true);
+ Doc.RemoveEmbedding(rdoc, rdoc);
}
});
if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
- (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
+ Array.from(targetDataDoc[DocViews])[0]?.ComponentView?.promoteCollection?.();
} else {
this.isAnyChildContentActive() && this._props.select(false);
}
@@ -264,7 +295,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
@action.bound
addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) {
+ if (this._props.filterAddDocument?.(docs) === false || docs.find(fdoc => Doc.AreProtosEqual(fdoc, this.Document) && Doc.LayoutField(fdoc) === Doc.LayoutField(this.Document))) {
return false;
}
const targetDataDoc = this.Document[DocData]; // this.dataDoc; // we want to write to the template, not the actual data doc
@@ -276,12 +307,12 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
const added = docs;
if (added.length) {
if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
- added.forEach(doc => {
- doc._dragOnlyWithinContainer = undefined;
- if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document;
- else doc[DocData].annotationOn = undefined;
- Doc.SetContainer(doc, this.Document);
- inheritParentAcls(targetDataDoc, doc, true);
+ added.forEach(adoc => {
+ adoc._dragOnlyWithinContainer = undefined;
+ if (annotationKey ?? this._annotationKeySuffix()) adoc[DocData].annotationOn = this.Document;
+ else adoc[DocData].annotationOn = undefined;
+ Doc.SetContainer(adoc, this.Document);
+ inheritParentAcls(targetDataDoc, adoc, true);
});
const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List<Doc>; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them, so we copy them below (should really be some kind of inheritance since the template contents could change)
@@ -295,7 +326,10 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
isAnyChildContentActive = () => this._isAnyChildContentActive;
- whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ this._props.whenChildContentsActiveChanged(isActive);
+ });
}
return Component;
}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 15ce4c15f..c007af6fa 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,36 +1,42 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { Popup } from 'browndash-components';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { FaEdit } from 'react-icons/fa';
+import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Cast, DocCast } from '../../fields/Types';
import { DocUtils } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { IsFollowLinkScript } from '../util/LinkFollower';
import { SelectionManager } from '../util/SelectionManager';
import { SharingManager } from '../util/SharingManager';
import { UndoManager, undoBatch } from '../util/UndoManager';
+import { PinProps } from './DocComponent';
import './DocumentButtonBar.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
+import { TemplateMenu } from './TemplateMenu';
import { TabDocView } from './collections/TabDocView';
import { Colors } from './global/globalEnums';
import { LinkPopup } from './linking/LinkPopup';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView';
import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { PinProps } from './nodes/trails';
-import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
-import { Popup } from 'browndash-components';
-import { TemplateMenu } from './TemplateMenu';
-import { FaEdit } from 'react-icons/fa';
@observer
export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
private _dragRef = React.createRef<HTMLDivElement>();
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: DocumentButtonBar;
constructor(props: any) {
@@ -58,8 +64,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
- onPointerLeave={action(e => (this.subPin = ''))}
+ onPointerEnter={action(() => {
+ this.subPin = allDocs ? 'All ' : '';
+ })}
+ onPointerLeave={action(() => {
+ this.subPin = '';
+ })}
onClick={e => {
this._props.views().forEach(dv => click(dv!.Document));
e.stopPropagation();
@@ -75,12 +85,14 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div
className="documentButtonBar-icon documentButtonBar-follow"
style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
- onClick={undoBatch(e => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
+ onClick={undoBatch(() => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
<div className="documentButtonBar-followTypes">
{followBtn(
true,
- (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks),
- (doc?: Doc) => (doc?.followAllLinks ? true : false),
+ (doc: Doc) => {
+ doc.followAllLinks = !doc.followAllLinks;
+ },
+ (doc?: Doc) => !!doc?.followAllLinks,
'window-maximize'
)}
</div>
@@ -98,22 +110,22 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div className="documentButtonBar-linkTypes">
<Tooltip title={<div>search for target</div>}>
<div className="documentButtonBar-button">
- <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon={'link'} size="lg" />
+ <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon="search" size="lg" />
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon="link" size="lg" />
</button>
</div>
</Tooltip>
<Tooltip title={<div>open linked trail</div>}>
<div className="documentButtonBar-button">
- <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
+ <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
<FontAwesomeIcon icon="taxi" size="lg" />
</button>
</div>
</Tooltip>
</div>
<div style={{ width: 25, height: 25 }}>
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink />
</div>
</div>
);
@@ -132,8 +144,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))}
- onPointerLeave={action(e => (this.subEndLink = ''))}
+ onPointerEnter={action(() => {
+ this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '');
+ })}
+ onPointerLeave={action(() => {
+ this.subEndLink = '';
+ })}
onClick={e => {
this.view0 &&
DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
@@ -155,7 +171,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{linkBtn(false, true, 'address-card')}
{linkBtn(true, true, 'id-card')}
</div>
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink={false} />
</div>
);
}
@@ -175,15 +191,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(
- e =>
- (this.subPin =
- (pinLayoutView ? 'Layout' : '') +
- (pinLayoutView && pinContentView ? ' &' : '') +
- (pinContentView ? ' Content View' : '') +
- (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''))
- )}
- onPointerLeave={action(e => (this.subPin = ''))}
+ onPointerEnter={action(() => {
+ this.subPin =
+ (pinLayoutView ? 'Layout' : '') +
+ (pinLayoutView && pinContentView ? ' &' : '') +
+ (pinContentView ? ' Content View' : '') +
+ (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
+ })}
+ onPointerLeave={action(() => {
+ this.subPin = '';
+ })}
onClick={e => {
const docs = this._props
.views()
@@ -230,8 +247,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get shareButton() {
const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+ <Tooltip title={<div className="dash-tooltip">Open Sharing Manager</div>}>
+ <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={() => SharingManager.Instance.open(this.view0, targetDoc)}>
<FontAwesomeIcon className="documentdecorations-icon" icon="users" />
</div>
</Tooltip>
@@ -242,8 +259,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get menuButton() {
const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
+ <Tooltip title={<div className="dash-tooltip">Open Context Menu</div>}>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => this.openContextMenu(clickEv))}>
<FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
</div>
</Tooltip>
@@ -258,8 +275,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div
className="documentButtonBar-icon"
style={{ color: 'white' }}
- onClick={e => {
- console.log('hi: ', CalendarManager.Instance);
+ onClick={() => {
CalendarManager.Instance.open(this.view0, targetDoc);
}}>
<FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup} />
@@ -280,7 +296,18 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }}
onPointerDown={action((e: React.PointerEvent) => {
this._isRecording = true;
- this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
+ this._props.views().map(
+ view =>
+ view &&
+ DocumentViewInternal.recordAudioAnnotation(
+ view.dataDoc,
+ view.LayoutFieldKey,
+ stopFunc => {
+ this._stopFunc = stopFunc;
+ },
+ emptyFunction
+ )
+ );
const b = UndoManager.StartBatch('Recording');
setupMoveUpEvents(
this,
@@ -308,10 +335,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
if (this._dragRef.current) {
const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
- const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
+ const origin = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
dragData.defaultDropAction = dropActionType.embed;
dragData.canEmbed = true;
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
+ DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, origin[0], origin[1], { hideSource: false });
return true;
}
return false;
@@ -334,8 +361,19 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
@computed
get templateButton() {
return !this.view0 ? null : (
- <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
- <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
+ <Tooltip
+ title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>}
+ open={this._tooltipOpen}
+ onClose={action(() => {
+ this._tooltipOpen = false;
+ })}
+ placement="bottom">
+ <div
+ className="documentButtonBar-linkFlyout"
+ ref={this._dragRef}
+ onPointerEnter={action(() => {
+ !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true);
+ })}>
<Popup icon={<FaEdit />} popup={this.templateMenu} popupContainsPt={returnTrue} />
</div>
</Tooltip>
@@ -363,17 +401,17 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
@observable _captureEndLinkLayout = false;
@action
- captureEndLinkLayout = (e: React.PointerEvent) => {
+ captureEndLinkLayout = () => {
this._captureEndLinkLayout = !this._captureEndLinkLayout;
};
@observable _captureEndLinkContent = false;
@action
- captureEndLinkContent = (e: React.PointerEvent) => {
+ captureEndLinkContent = () => {
this._captureEndLinkContent = !this._captureEndLinkContent;
};
@action
- captureEndLinkState = (e: React.PointerEvent) => {
+ captureEndLinkState = () => {
this._captureEndLinkContent = this._captureEndLinkLayout = !this._captureEndLinkLayout;
};
@@ -400,13 +438,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
return (
<div className="documentButtonBar">
<div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu ShowCount />
</div>
{this._showLinkPopup ? (
<div style={{ position: 'absolute', zIndex: 1000 }}>
<LinkPopup
key="popup"
- linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))}
+ linkCreated={link => {
+ link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick);
+ }}
linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
linkFrom={() => this._props.views().lastElement()?.Document}
/>
@@ -421,7 +461,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div className="documentButtonBar-button">{this.pinButton}</div>
<div className="documentButtonBar-button">{this.recordButton}</div>
<div className="documentButtonBar-button">{this.calendarButton}</div>
- {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
+ {!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
<div className="documentButtonBar-button">{this.menuButton}</div>
</div>
);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2a44a9739..3083b9be0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,9 +5,10 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaUndo } from 'react-icons/fa';
-import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { lightOrDark, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, numberValue, emptyFunction } from '../../Utils';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
import { InkField } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
@@ -34,7 +35,7 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
import { KeyValueBox } from './nodes/KeyValueBox';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { identity } from 'lodash';
+import { Id } from '../../fields/FieldSymbols';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -44,6 +45,7 @@ interface DocumentDecorationsProps {
}
@observer
export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: DocumentDecorations;
private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
@@ -123,7 +125,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
- SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
+ SelectionManager.Docs.forEach(doc => {
+ doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
+ });
}
const titleFieldKey = this._titleControlString.substring(1);
UndoManager.RunInBatch(
@@ -149,21 +153,21 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
onContainerDown = (e: React.PointerEvent) => {
const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
- if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
+ if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
+ setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(true, moveEv), emptyFunction, emptyFunction);
e.stopPropagation();
}
};
onTitleDown = (e: React.PointerEvent) => {
const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
- if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
setupMoveUpEvents(
this,
e,
- e => this.onBackgroundMove(true, e),
+ moveEv => this.onBackgroundMove(true, moveEv),
emptyFunction,
- action(e => {
+ action(() => {
const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
!this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
this._editingTitle = true;
@@ -175,14 +179,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
onBackgroundDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
+ setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(false, moveEv), emptyFunction, emptyFunction);
e.stopPropagation();
};
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
const dragDocView = SelectionManager.Views[0];
const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
- if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
+ if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) {
return false;
}
const containers = new Set<Doc | undefined>();
@@ -205,7 +209,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
e.x,
e.y,
{
- dragComplete: action(e => (this._hidden = false)),
+ dragComplete: action(() => {
+ this._hidden = false;
+ }),
hideSource: true,
}
);
@@ -217,15 +223,16 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0);
if (forceDeleteOrIconify === false && this._iconifyBatch) return;
- this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
- var iconifyingCount = views.length;
+ this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch);
+ let iconifyingCount = views.length;
const finished = action((force?: boolean) => {
if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
if (this._deleteAfterIconify) {
views.forEach(iconView => {
- Doc.setNativeView(iconView.Document);
- if (iconView.Document.activeFrame) {
- iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+ const iconViewDoc = iconView.Document;
+ Doc.setNativeView(iconViewDoc);
+ if (iconViewDoc.activeFrame) {
+ iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
} else {
iconView._props.removeDocument?.(iconView.Document);
}
@@ -240,6 +247,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
(document.activeElement as any).blur?.();
this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying');
} else {
+ // eslint-disable-next-line no-param-reassign
forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
}
@@ -268,7 +276,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
// open same document in new tab
CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right);
} else {
- var openDoc = selectedDocs[0].Document;
+ let openDoc = selectedDocs[0].Document;
if (openDoc.layout_fieldKey === 'layout_icon') {
openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
Doc.deiconifyView(openDoc);
@@ -294,24 +302,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
*/
@action
onRadiusDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
+ SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]);
this._isRounding = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const [x, y] = [this.Bounds.x + 3, this.Bounds.y + 3];
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
- const dist = e.clientX < x && e.clientY < y ? 0 : Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
- SelectionManager.Docs.map(doc => {
+ const dist = moveEv.clientX < x && moveEv.clientY < y ? 0 : Math.sqrt((moveEv.clientX - x) * (moveEv.clientX - x) + (moveEv.clientY - y) * (moveEv.clientY - y));
+ SelectionManager.Docs.forEach(doc => {
const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
doc._layout_borderRounding = `${radius}px`;
});
return false;
},
- action(e => {
+ action(() => {
SnappingManager.SetIsResizing(undefined);
this._isRounding = false;
this._resizeUndo?.end();
@@ -329,17 +337,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
e,
returnFalse, // don't care about move or up event,
emptyFunction, // just care about whether we get a click event
- e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
+ () => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
);
e.stopPropagation();
};
setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
+ const selDoc = seldocview.Document;
const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
- seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
- seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+ selDoc._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+ selDoc._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
};
@action
@@ -349,10 +358,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
+ (moveEv: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean,
- action(e => (this._isRotating = false)), // upEvent
- action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)),
+ action(() => { this._isRotating = false; }), // upEvent
+ action(() => { seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0; }),
true
); // prettier-ignore
e.stopPropagation();
@@ -390,9 +399,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- const previousPoint = { X: e.clientX, Y: e.clientY };
- const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
+ (moveEv: PointerEvent, down: number[], delta: number[]) => {
+ const previousPoint = { X: moveEv.clientX, Y: moveEv.clientY };
+ const movedPoint = { X: moveEv.clientX - delta[0], Y: moveEv.clientY - delta[1] };
const deltaAng = InkStrokeProperties.angleChange(movedPoint, previousPoint, rcScreen);
if (selectedInk.length) {
deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, deltaAng, rcScreen);
@@ -418,13 +427,15 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._isRotating = false;
rotateUndo?.end();
}), // upEvent
- action(e => (this._showRotCenter = !this._showRotCenter)) // clickEvent
+ action(() => {
+ this._showRotCenter = !this._showRotCenter;
+ }) // clickEvent
);
};
@action
onPointerDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+ SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
const id = (this._resizeHdlId = e.currentTarget.className);
@@ -453,12 +464,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
};
- onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
+ onPointerMove = (e: PointerEvent): boolean => {
const first = SelectionManager.Views[0];
const effectiveAcl = GetEffectiveAcl(first.Document);
- if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
+ if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false;
if (!first) return false;
- var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+ const fixedAspect = Doc.NativeAspect(first.layoutDoc);
const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
const thisPt = // do snapping of drag point
@@ -472,11 +483,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._interactionLock = true;
this._snapPt = thisPt;
e.ctrlKey && (SelectionManager.Views.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
- const fixedAspect = SelectionManager.Docs.some(this.hasFixedAspect);
- const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y};
+ const hasFixedAspect = SelectionManager.Docs.some(this.hasFixedAspect);
+ const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
SelectionManager.Views.forEach(docView =>
this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
- await new Promise<any>(res => setTimeout(() => res(this._interactionLock = undefined)));
+ await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
}); // prettier-ignore
return false;
@@ -553,10 +564,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
doc._layout_modificationDate = new DateField();
if (scale.y !== 1) {
- docView.layoutDoc._layout_autoHeight = undefined;
+ const docLayout = docView.layoutDoc;
+ docLayout._layout_autoHeight = undefined;
if (docView.layoutDoc._layout_autoHeight) {
// if autoHeight is still on because of a prototype
- docView.layoutDoc._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
+ docLayout._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
}
}
}
@@ -586,15 +598,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
@action
- onPointerUp = (e: PointerEvent): void => {
+ onPointerUp = (): void => {
SnappingManager.SetIsResizing(undefined);
SnappingManager.clearSnapLines();
this._resizeHdlId = '';
this._resizeUndo?.end();
// detect layout_autoHeight gesture and apply
- SelectionManager.Views.forEach(view => NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true));
- //need to change points for resize, or else rotation/control points will fail.
+ SelectionManager.Views.forEach(view => {
+ NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
+ });
+ // need to change points for resize, or else rotation/control points will fail.
this._inkDragDocs
.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
.forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
@@ -613,7 +627,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
if (SelectionManager.Views.length === 1) {
const selected = SelectionManager.Views[0];
if (this._titleControlString.startsWith('$')) {
- return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+ return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
}
return this._accumulatedTitle;
}
@@ -648,7 +662,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document);
const docShareMode = HierarchyMapping.get(acl)!.name;
const shareMode = StrCast(docShareMode);
- var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
+ const shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
// hide the decorations if the parent chooses to hide it or if the document itself hides it
const hideDecorations = SnappingManager.IsResizing || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
@@ -678,7 +692,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
});
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)))}>
+ <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => click!(clickEv)))}>
<FontAwesomeIcon icon={icon as any} />
</div>
</Tooltip>
@@ -687,7 +701,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const bounds = this.ClippedBounds;
const useLock = bounds.r - bounds.x > 135;
const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
- const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
+ const rotation = SelectionManager.Views.length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
// Radius constants
const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -727,11 +741,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
name="dynbox"
autoComplete="on"
value={hideTitle ? '' : this._accumulatedTitle}
- onBlur={action((e: React.FocusEvent) => {
+ onBlur={action(() => {
this._editingTitle = false;
this.titleBlur();
})}
- onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onChange={action(e => {
+ !hideTitle && (this._accumulatedTitle = e.target.value);
+ })}
onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
onPointerDown={e => e.stopPropagation()}
/>
@@ -789,8 +805,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
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')}
+ {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
+ {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
{titleArea}
{hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 85e893e19..684b948af 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -70,7 +72,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
constructor(props: EditableProps) {
super(props);
makeObservable(this);
- this._editing = this._props.editing ? true : false;
+ this._editing = !!this._props.editing;
}
componentDidMount(): void {
@@ -166,7 +168,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
break;
}
-
+ // eslint-disable-next-line no-fallthrough
default:
if (this._props.textCallback?.(e.key)) {
e.stopPropagation();
@@ -186,7 +188,6 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
this._editing = true;
this._props.isEditingCallback?.(true);
}
- // e.stopPropagation();
}
};
@@ -223,6 +224,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
renderEditor() {
return this._props.autosuggestProps ? (
<Autosuggest
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props.autosuggestProps.autosuggestProps}
inputProps={{
className: 'editableView-input',
@@ -241,12 +243,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
<input
className="editableView-input"
- ref={r => (this._inputref = r)}
+ ref={r => { this._inputref = r; }} // prettier-ignore
style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }}
placeholder={this._props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
defaultValue={this._props.GetValue()}
- autoFocus={true}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={this.stopPropagation}
@@ -256,12 +259,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
) : (
<textarea
className="editableView-input"
- ref={r => (this._inputref = r)}
+ ref={r => { this._inputref = r; }} // prettier-ignore
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}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={this.stopPropagation}
@@ -304,7 +308,10 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
fontStyle: this._props.fontStyle,
fontSize: this._props.fontSize,
}}>
- {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''}
+ {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''
+ }
</span>
</div>
);
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 6a5c2cb4c..3cb7848c2 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -64,7 +64,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
return (
<Select
styles={{
- control: (baseStyles, state) => ({
+ control: (baseStyles /* , state */) => ({
...baseStyles,
minHeight: '5px',
maxHeight: '30px',
@@ -73,17 +73,17 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
padding: 0,
margin: 0,
}),
- singleValue: (baseStyles, state) => ({
+ singleValue: (baseStyles /* , state */) => ({
...baseStyles,
color: SettingsManager.userColor,
background: SettingsManager.userBackgroundColor,
}),
- placeholder: (baseStyles, state) => ({
+ placeholder: (baseStyles /* , state */) => ({
...baseStyles,
color: SettingsManager.userColor,
background: SettingsManager.userBackgroundColor,
}),
- input: (baseStyles, state) => ({
+ input: (baseStyles /* , state */) => ({
...baseStyles,
padding: 0,
margin: 0,
@@ -95,7 +95,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
color: SettingsManager.userColor,
background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
}),
- menuList: (baseStyles, state) => ({
+ menuList: (baseStyles /* , state */) => ({
...baseStyles,
backgroundColor: SettingsManager.userBackgroundColor,
}),
@@ -106,12 +106,14 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
onChange={val => this._props.selectFunc((val as any as { value: string; label: string }).value)}
onKeyDown={e => {
if (e.key === 'Enter') {
- runInAction(() => this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value)));
+ runInAction(() => {
+ this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value));
+ });
}
e.stopPropagation();
}}
onMenuClose={this._props.menuClose}
- closeMenuOnSelect={true}
+ closeMenuOnSelect
value={this._props.showPlaceholder ? null : undefined}
/>
);
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0521c4a4b..994107c01 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
-import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
@@ -58,7 +61,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
@computed get mapActiveFiltersToFacets() {
const filters = new Map<string, string>();
- //this.targetDoc.docFilters
+ // this.targetDoc.docFilters
this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0]));
return filters;
}
@@ -73,7 +76,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// ["#tags", "width", "height"]
//
@computed get activeFacetHeaders() {
- const activeHeaders = new Array();
+ const activeHeaders = [] as string[];
this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]));
return activeHeaders;
@@ -83,19 +86,20 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
let rtFields = 0;
let subDocs = childDocs;
- let gatheredDocs = [] as Doc[];
+ const gatheredDocs = [] as Doc[];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
while (subDocs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
gatheredDocs.push(t);
const facetVal = t[facetKey];
if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
- facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
- (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal));
+ facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
+ (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
@@ -115,7 +119,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
@observable _chosenFacetsCollapse = new ObservableMap<string, boolean>();
- @observable _collapseReturnKeys = new Array();
+ @observable _collapseReturnKeys = [] as string[];
// this computed function gets the active filters and maps them to their headers
//
@@ -130,11 +134,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
let nonNumbers = 0;
- let minVal = Number.MAX_VALUE,
- maxVal = -Number.MAX_VALUE;
- facetValues.strings.map(val => {
+ let minVal = Number.MAX_VALUE;
+ let maxVal = -Number.MAX_VALUE;
+ facetValues.strings.forEach(val => {
const num = val ? Number(val) : Number.NaN;
- if (Number.isNaN(num)) {
+ if (isNaN(num)) {
val && nonNumbers++;
} else {
minVal = Math.min(num, minVal);
@@ -144,14 +148,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
if (facetHeader === 'text') {
return { facetHeader, renderType: 'text' };
- } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
+ }
+ if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
- const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
- return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
- } else {
- return { facetHeader, renderType: 'checkbox' };
+ const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
+ return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged || [extendedMinVal, extendedMaxVal] };
}
+ return { facetHeader, renderType: 'checkbox' };
})
);
}
@@ -171,17 +175,17 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
sortingCurrentFacetValues = (facetHeader: string) => {
this._collapseReturnKeys.splice(0);
- Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => {
+ Array.from(this.activeRenderedFacetInfos.keys()).forEach(renderInfo => {
if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) {
- this._collapseReturnKeys.push(renderInfo.range.map(number => number.toFixed(2)));
+ this._collapseReturnKeys.push(...renderInfo.range.map(number => number.toFixed(2)));
}
});
- for (var key of this.facetValues(facetHeader)) {
+ this.facetValues(facetHeader).forEach(key => {
if (this.mapActiveFiltersToFacets.get(key)) {
this._collapseReturnKeys.push(key);
}
- }
+ });
return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(', ')}</div>;
};
@@ -198,7 +202,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
);
else
allCollectionDocs.forEach(child => {
- const fieldVal = child[facetHeader] as Field;
+ const fieldVal = child[facetHeader] as FieldType;
if (!(fieldVal instanceof List)) {
// currently we have no good way of filtering based on a field that is a list
set.add(Field.toString(fieldVal));
@@ -209,7 +213,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
let nonNumbers = 0;
- facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
+ facetValues.map(val => isNaN(Number(val)) && nonNumbers++);
return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
};
@@ -218,7 +222,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div className="filterBox-treeView">
<div className="filterBox-select">
<div style={{ width: '100%' }}>
- <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
+ <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
</div>
{/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
{/* <div className="filterBox-select-bool">
@@ -234,34 +238,31 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div className="filterBox-tree" key="tree">
{Array.from(this.activeRenderedFacetInfos.keys()).map(
- (
- renderInfo // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader
- ) => (
+ // iterate over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader
+ renderInfo => (
<div>
<div className="filterBox-facetHeader">
<div className="filterBox-facetHeader-Header"> </div>
{renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)}
-
<div
className="filterBox-facetHeader-collapse"
- onClick={action(e => {
+ onClick={action(() => {
const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader);
this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue);
})}>
{this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />}
</div>
-
<div
className="filterBox-facetHeader-remove"
- onClick={action(e => {
+ onClick={action(() => {
if (renderInfo.facetHeader === 'text') {
Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove');
} else {
- for (var key of this.facetValues(renderInfo.facetHeader)) {
+ this.facetValues(renderInfo.facetHeader).forEach((key: string) => {
if (this.mapActiveFiltersToFacets.get(key)) {
Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove');
}
- }
+ });
}
this._selectedFacetHeaders.delete(renderInfo.facetHeader);
this._chosenFacetsCollapse.delete(renderInfo.facetHeader);
@@ -292,7 +293,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
return (
<input
key={this.Document[Id]}
- placeholder={'enter text to match'}
+ placeholder="enter text to match"
defaultValue={
StrListCast(this.Document._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
@@ -300,7 +301,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
}
style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
- onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
+ onKeyDown={e => e.key === 'Enter' && undoable(() => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')()}
/>
);
case 'checkbox':
@@ -312,7 +313,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
style={{ width: 20, marginLeft: 20 }}
checked={['check', 'exists'].includes(
StrListCast(this.Document._childFilters)
- .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
+ .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] === facetValue)
?.split(Doc.FilterSep)[2] ?? ''
)}
type={type}
@@ -324,55 +325,56 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
});
case 'range':
- const domain = renderInfoDomain;
- const range = renderInfoRange;
- if (domain) {
- return (
- <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
- <Slider
- mode={2}
- step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
- domain={[domain[0], domain[1]]} // -1000, 1000
- rootStyle={{ position: 'relative', width: '100%' }}
- onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
- values={renderInfoRange!}>
- <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
- <Handles>
- {({ handles, activeHandleID, getHandleProps }) => (
- <div className="slider-handles">
- {handles.map((handle, i) => {
- // const value = i === 0 ? defaultValues[0] : defaultValues[1];
- return (
+ {
+ const domain = renderInfoDomain;
+ if (domain) {
+ return (
+ <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
+ <Slider
+ mode={2}
+ step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
+ domain={[domain[0], domain[1]]} // -1000, 1000
+ rootStyle={{ position: 'relative', width: '100%' }}
+ onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
+ values={renderInfoRange!}>
+ <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
+ <Handles>
+ {({ handles, activeHandleID, getHandleProps }) => (
+ <div className="slider-handles">
+ {handles.map(handle => (
+ // const value = i === 0 ? defaultValues[0] : defaultValues[1];
<div>
<Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
</div>
- );
- })}
- </div>
- )}
- </Handles>
- <Tracks left={false} right={false}>
- {({ tracks, getTrackProps }) => (
- <div className="slider-tracks">
- {tracks.map(({ id, source, target }) => (
- <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
- ))}
- </div>
- )}
- </Tracks>
- <Ticks count={5}>
- {({ ticks }) => (
- <div className="slider-ticks">
- {ticks.map(tick => (
- <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
- ))}
- </div>
- )}
- </Ticks>
- </Slider>
- </div>
- );
+ ))}
+ </div>
+ )}
+ </Handles>
+ <Tracks left={false} right={false}>
+ {({ tracks, getTrackProps }) => (
+ <div className="slider-tracks">
+ {tracks.map(({ id, source, target }) => (
+ <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
+ ))}
+ </div>
+ )}
+ </Tracks>
+ <Ticks count={5}>
+ {({ ticks }) => (
+ <div className="slider-ticks">
+ {ticks.map(tick => (
+ <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
+ ))}
+ </div>
+ )}
+ </Ticks>
+ </Slider>
+ </div>
+ );
+ }
}
+ break;
+ default:
// case 'range'
// return <Slider ...
@@ -386,5 +388,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// <dimain changing handles >
// <?div
}
+ return undefined;
}
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 86b9f5e40..46fa04d72 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,11 +2,13 @@ import * as fitCurve from 'fit-curve';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
import { InkData, InkTool } from '../../fields/InkField';
import { BoolCast, NumCast } from '../../fields/Types';
import MobileInkOverlay from '../../mobile/MobileInkOverlay';
+import { Gestures } from '../../pen-gestures/GestureTypes';
import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { MobileInkOverlayContent } from '../../server/Message';
import { InteractionUtils } from '../util/InteractionUtils';
@@ -29,15 +31,22 @@ import {
SetActiveInkWidth,
} from './InkingStroke';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { checkInksToGroup } from './global/globalScripts';
import { DocumentView } from './nodes/DocumentView';
+export enum ToolglassTools {
+ InkToText = 'inktotext',
+ IgnoreGesture = 'ignoregesture',
+ RadialMenu = 'radialmenu',
+ None = 'none',
+}
interface GestureOverlayProps {
isActive: boolean;
}
@observer
export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GestureOverlay;
+ // eslint-disable-next-line no-use-before-define
static Instances: GestureOverlay[] = [];
public static set RecognizeGestures(active) {
@@ -47,7 +56,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
return BoolCast(Doc.UserDoc().recognizeGestures);
}
- @observable public InkShape: Opt<GestureUtils.Gestures> = undefined;
+ @observable public InkShape: Opt<Gestures> = undefined;
@observable public SavedColor?: string = undefined;
@observable public SavedWidth?: number = undefined;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -55,9 +64,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
@observable private _thumbX?: number = undefined;
@observable private _thumbY?: number = undefined;
- @observable private _selectedIndex: number = -1;
- @observable private _menuX: number = -300;
- @observable private _menuY: number = -300;
@observable private _pointerY?: number = undefined;
@observable private _points: { X: number; Y: number }[] = [];
@observable private _strokes: InkData[] = [];
@@ -117,9 +123,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
- case ToolglassTools.RadialMenu:
- return true;
- }
+ case ToolglassTools.RadialMenu: return true;
+ default:
+ } // prettier-ignore
}
}
return false;
@@ -128,8 +134,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
@action primCreated() {
if (!this.KeepPrimitiveMode) {
this.InkShape = undefined;
- //get out of ink mode after each stroke=
- //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+ // get out of ink mode after each stroke=
+ // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
Doc.ActiveTool = InkTool.None;
// SetActiveArrowStart('none');
// SetActiveArrowEnd('none');
@@ -142,7 +148,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
const B = this.svgBounds;
const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
- //if any of the shape is activated in the CollectionFreeFormViewChrome
+ // if any of the shape is activated in the CollectionFreeFormViewChrome
if (this.InkShape) {
this.makeBezierPolygon(this.InkShape, false);
this.dispatchGesture(this.InkShape);
@@ -155,16 +161,17 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
let actionPerformed = false;
if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Line:
- case GestureUtils.Gestures.Triangle:
- case GestureUtils.Gestures.Rectangle:
- case GestureUtils.Gestures.Circle:
+ case Gestures.Line:
+ case Gestures.Triangle:
+ case Gestures.Rectangle:
+ case Gestures.Circle:
this.makeBezierPolygon(result.Name, true);
actionPerformed = this.dispatchGesture(result.Name);
break;
- case GestureUtils.Gestures.Scribble:
+ case Gestures.Scribble:
console.log('scribble');
break;
+ default:
}
}
@@ -178,21 +185,20 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
const controlPoints: { X: number; Y: number }[] = [];
const bezierCurves = (fitCurve as any)(newPoints, 10);
- for (const curve of bezierCurves) {
+ Array.from(bezierCurves).forEach((curve: any) => {
controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
- }
+ });
const dist = Math.sqrt(
(controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)
);
+ // eslint-disable-next-line prefer-destructuring
if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
this._points.length = 0;
this._points.push(...controlPoints);
- this.dispatchGesture(GestureUtils.Gestures.Stroke);
- // TODO: nda - check inks to group here
- checkInksToGroup();
+ this.dispatchGesture(Gestures.Stroke);
}
}
}
@@ -202,34 +208,34 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
makeBezierPolygon = (shape: string, gesture: boolean) => {
const xs = this._points.map(p => p.X);
const ys = this._points.map(p => p.Y);
- var right = Math.max(...xs);
- var left = Math.min(...xs);
- var bottom = Math.max(...ys);
- var top = Math.min(...ys);
+ let right = Math.max(...xs);
+ let left = Math.min(...xs);
+ let bottom = Math.max(...ys);
+ let top = Math.min(...ys);
const firstx = this._points[0].X;
const firsty = this._points[0].Y;
- var lastx = this._points[this._points.length - 2].X;
- var lasty = this._points[this._points.length - 2].Y;
- var fourth = (lastx - firstx) / 4;
+ let lastx = this._points[this._points.length - 2].X;
+ let lasty = this._points[this._points.length - 2].Y;
+ let fourth = (lastx - firstx) / 4;
if (isNaN(fourth) || fourth === 0) {
fourth = 0.01;
}
- var m = (lasty - firsty) / (lastx - firstx);
+ let m = (lasty - firsty) / (lastx - firstx);
if (isNaN(m) || m === 0) {
m = 0.01;
}
- const b = firsty - m * firstx;
+ // const b = firsty - m * firstx;
if (shape === 'noRec') {
return false;
}
if (!gesture) {
- //if shape options is activated in inkOptionMenu
- //take second to last point because _point[length-1] is _points[0]
+ // if shape options is activated in inkOptionMenu
+ // take second to last point because _point[length-1] is _points[0]
right = this._points[this._points.length - 2].X;
left = this._points[0].X;
bottom = this._points[this._points.length - 2].Y;
top = this._points[0].Y;
- if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
+ if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
if (left > right) {
const temp = right;
right = left;
@@ -244,7 +250,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
}
this._points.length = 0;
switch (shape) {
- case GestureUtils.Gestures.Rectangle:
+ case Gestures.Rectangle:
this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: top });
@@ -267,7 +273,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
break;
- case GestureUtils.Gestures.Triangle:
+ case Gestures.Triangle:
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -285,39 +291,40 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._points.push({ X: left, Y: bottom });
break;
- case GestureUtils.Gestures.Circle:
- // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
- // making the curves indistinguishable from a circle.
- // Source: https://spencermortensen.com/articles/bezier-circle/
- const c = 0.551915024494;
- const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
- const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
- const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-
- // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
- this._points.push({ X: centerX, Y: centerY + radius });
- this._points.push({ X: centerX + c * radius, Y: centerY + radius });
- this._points.push({ X: centerX + radius, Y: centerY + c * radius });
- this._points.push({ X: centerX + radius, Y: centerY });
-
- this._points.push({ X: centerX + radius, Y: centerY });
- this._points.push({ X: centerX + radius, Y: centerY - c * radius });
- this._points.push({ X: centerX + c * radius, Y: centerY - radius });
- this._points.push({ X: centerX, Y: centerY - radius });
-
- this._points.push({ X: centerX, Y: centerY - radius });
- this._points.push({ X: centerX - c * radius, Y: centerY - radius });
- this._points.push({ X: centerX - radius, Y: centerY - c * radius });
- this._points.push({ X: centerX - radius, Y: centerY });
-
- this._points.push({ X: centerX - radius, Y: centerY });
- this._points.push({ X: centerX - radius, Y: centerY + c * radius });
- this._points.push({ X: centerX - c * radius, Y: centerY + radius });
- this._points.push({ X: centerX, Y: centerY + radius });
-
+ case Gestures.Circle:
+ {
+ // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
+ // making the curves indistinguishable from a circle.
+ // Source: https://spencermortensen.com/articles/bezier-circle/
+ const c = 0.551915024494;
+ const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+ const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+ const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+
+ // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
+ this._points.push({ X: centerX, Y: centerY + radius });
+ this._points.push({ X: centerX + c * radius, Y: centerY + radius });
+ this._points.push({ X: centerX + radius, Y: centerY + c * radius });
+ this._points.push({ X: centerX + radius, Y: centerY });
+
+ this._points.push({ X: centerX + radius, Y: centerY });
+ this._points.push({ X: centerX + radius, Y: centerY - c * radius });
+ this._points.push({ X: centerX + c * radius, Y: centerY - radius });
+ this._points.push({ X: centerX, Y: centerY - radius });
+
+ this._points.push({ X: centerX, Y: centerY - radius });
+ this._points.push({ X: centerX - c * radius, Y: centerY - radius });
+ this._points.push({ X: centerX - radius, Y: centerY - c * radius });
+ this._points.push({ X: centerX - radius, Y: centerY });
+
+ this._points.push({ X: centerX - radius, Y: centerY });
+ this._points.push({ X: centerX - radius, Y: centerY + c * radius });
+ this._points.push({ X: centerX - c * radius, Y: centerY + radius });
+ this._points.push({ X: centerX, Y: centerY + radius });
+ }
break;
- case GestureUtils.Gestures.Line:
+ case Gestures.Line:
if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
lastx = firstx;
}
@@ -330,28 +337,32 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._points.push({ X: lastx, Y: lasty });
this._points.push({ X: lastx, Y: lasty });
break;
- case GestureUtils.Gestures.Arrow:
- const x1 = left;
- const y1 = top;
- const x2 = right;
- const y2 = bottom;
- const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2));
- const L2 = L1 / 5;
- const angle = 0.785398;
- const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
- const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
- const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
- const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
- this._points.push({ X: x1, Y: y1 });
- this._points.push({ X: x2, Y: y2 });
- this._points.push({ X: x3, Y: y3 });
- this._points.push({ X: x4, Y: y4 });
- this._points.push({ X: x2, Y: y2 });
+ case Gestures.Arrow:
+ {
+ const x1 = left;
+ const y1 = top;
+ const x2 = right;
+ const y2 = bottom;
+ const L1 = Math.sqrt(Math.abs(x1 - x2) ** 2 + Math.abs(y1 - y2) ** 2);
+ const L2 = L1 / 5;
+ const angle = 0.785398;
+ const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
+ const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
+ const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
+ const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
+ this._points.push({ X: x1, Y: y1 });
+ this._points.push({ X: x2, Y: y2 });
+ this._points.push({ X: x3, Y: y3 });
+ this._points.push({ X: x4, Y: y4 });
+ this._points.push({ X: x2, Y: y2 });
+ }
+ break;
+ default:
}
return false;
};
- dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => {
+ dispatchGesture = (gesture: Gestures, stroke?: InkData, text?: any) => {
const points = (stroke ?? this._points).slice();
return (
document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent(
@@ -387,11 +398,11 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
const selView = GestureOverlay.DownDocView;
const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
const rect = this._overlayRef.current?.getBoundingClientRect();
- const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
- B.left = B.left - width / 2;
- B.right = B.right + width / 2;
+ const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true);
+ B.left -= width / 2;
+ B.right += width / 2;
B.top = B.top - width / 2 - (rect?.y || 0);
- B.bottom = B.bottom + width / 2;
+ B.bottom += width / 2;
B.width += width;
B.height += width;
const fillColor = ActiveFillColor();
@@ -401,7 +412,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._palette,
[
this._strokes.map((l, i) => {
- const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true);
+ const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true);
return (
<svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
{InteractionUtils.CreatePolyline(
@@ -414,7 +425,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
'miter',
'round',
ActiveInkBezierApprox(),
- 'none' /*ActiveFillColor()*/,
+ 'none' /* ActiveFillColor() */,
ActiveArrowStart(),
ActiveArrowEnd(),
ActiveArrowScale(),
@@ -441,7 +452,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
'miter',
'round',
'',
- 'none' /*ActiveFillColor()*/,
+ 'none' /* ActiveFillColor() */,
ActiveArrowStart(),
ActiveArrowEnd(),
ActiveArrowScale(),
@@ -529,19 +540,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
}
}
-// export class
-
-export enum ToolglassTools {
- InkToText = 'inktotext',
- IgnoreGesture = 'ignoregesture',
- RadialMenu = 'radialmenu',
- None = 'none',
-}
-
ScriptingGlobals.add('GestureOverlay', GestureOverlay);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setToolglass(tool: any) {
- runInAction(() => (GestureOverlay.Instance.Tool = tool));
+ runInAction(() => {
+ GestureOverlay.Instance.Tool = tool;
+ });
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
runInAction(() => {
GestureOverlay.Instance.SavedColor = ActiveInkColor();
@@ -554,6 +560,7 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta
SetActiveDash(dash);
});
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function resetPen() {
runInAction(() => {
SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
@@ -561,8 +568,9 @@ ScriptingGlobals.add(function resetPen() {
});
}, 'resets the pen tool');
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function createText(text: any, x: any, y: any) {
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: x, Y: y }], text);
+ GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X: x, Y: y }], text);
},
'creates a text document with inputted text and coordinates',
'(text: any, x: any, y: any)'
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2f64ea28c..ebd61db7d 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -12,7 +12,7 @@ import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoable } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
import { InkStrokeProperties } from './InkStrokeProperties';
@@ -27,15 +27,13 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { AnchorMenu } from './pdf/AnchorMenu';
const modifiers = ['control', 'meta', 'shift', 'alt'];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
type KeyControlInfo = {
preventDefault: boolean;
stopPropagation: boolean;
};
-
-export let CtrlKey = false;
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
export class KeyManager {
- public static Instance: KeyManager = new KeyManager();
+ public static Instance = new KeyManager();
private router = new Map<string, KeyHandler>();
constructor() {
@@ -49,8 +47,8 @@ export class KeyManager {
this.router.set('1000', this.shift);
}
- public unhandle = action((e: KeyboardEvent) => {
- e.key === 'Control' && (CtrlKey = false);
+ public unhandle = action((/* e: KeyboardEvent */) => {
+ /* empty */
});
public handleModifiers = action((e: KeyboardEvent) => {
if (e.shiftKey) SnappingManager.SetShiftKey(true);
@@ -65,11 +63,9 @@ export class KeyManager {
public handle = action((e: KeyboardEvent) => {
// accumulate buffer of characters to insert in a new text note. once the note is created, it will stop keyboard events from reaching this function.
- if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar = FormattedTextBox.SelectOnLoadChar + (e.key === 'Enter' ? '\n' : e.key);
- e.key === 'Control' && (CtrlKey = true);
- //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
+ if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar += e.key === 'Enter' ? '\n' : e.key;
const keyname = e.key && e.key.toLowerCase();
- this.handleGreedy(keyname);
+ this.handleGreedy(/* keyname */);
if (modifiers.includes(keyname)) {
return;
@@ -89,10 +85,7 @@ export class KeyManager {
control.preventDefault && e.preventDefault();
});
- private handleGreedy = action((keyname: string) => {
- switch (keyname) {
- }
- });
+ private handleGreedy = action((/* keyname: string */) => {});
nudge = (x: number, y: number, label: string) => {
const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge);
@@ -105,60 +98,55 @@ export class KeyManager {
case 'u':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
const ungroupings = SelectionManager.Views;
- UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
+ undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
SelectionManager.DeselectAll();
}
break;
case 'g':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
const selected = SelectionManager.Views;
- const collectionView = selected.reduce((col, dv) => (col === null || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView);
- if (collectionView) {
- UndoManager.RunInBatch(() =>
- collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs)
- , 'grouping');
- break;
- }
+ const cv = selected.reduce((col, dv) => (!col || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), undefined as undefined | CollectionFreeFormView);
+ cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs), 'grouping');
}
break;
case ' ':
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
- case 'escape':
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- InkStrokeProperties.Instance._controlButton = false;
- Doc.ActiveTool = InkTool.None;
- DragManager.CompleteWindowDrag?.(true);
- var doDeselect = true;
- if (SnappingManager.IsDragging) {
- DragManager.AbortDrag();
- }
- if (CollectionDockingView.Instance?.HasFullScreen) {
- CollectionDockingView.Instance?.CloseFullScreen();
- }
- if (CollectionStackedTimeline.SelectingRegions.size) {
- CollectionStackedTimeline.StopSelecting();
- doDeselect = false;
- } else {
- doDeselect = !ContextMenu.Instance.closeMenu();
- }
- if (doDeselect) {
- SelectionManager.DeselectAll();
- LightboxView.Instance.SetLightboxDoc(undefined);
+ case 'escape': {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ InkStrokeProperties.Instance._controlButton = false;
+ Doc.ActiveTool = InkTool.None;
+ DragManager.CompleteWindowDrag?.(true);
+ let doDeselect = true;
+ if (SnappingManager.IsDragging) {
+ DragManager.AbortDrag();
+ }
+ if (CollectionDockingView.Instance?.HasFullScreen) {
+ CollectionDockingView.Instance?.CloseFullScreen();
+ }
+ if (CollectionStackedTimeline.SelectingRegions.size) {
+ CollectionStackedTimeline.StopSelecting();
+ doDeselect = false;
+ } else {
+ doDeselect = !ContextMenu.Instance.closeMenu();
+ }
+ if (doDeselect) {
+ SelectionManager.DeselectAll();
+ LightboxView.Instance.SetLightboxDoc(undefined);
+ }
+ // DictationManager.Controls.stop();
+ GoogleAuthenticationManager.Instance.cancel();
+ SharingManager.Instance.close();
+ if (!GroupManager.Instance.isOpen) SettingsManager.Instance.closeMgr();
+ GroupManager.Instance.close();
+ window.getSelection()?.empty();
+ document.body.focus();
}
- // DictationManager.Controls.stop();
- GoogleAuthenticationManager.Instance.cancel();
- SharingManager.Instance.close();
- if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
- GroupManager.Instance.close();
- window.getSelection()?.empty();
- document.body.focus();
break;
- case 'enter': {
- //DocumentDecorations.Instance.onCloseClick(false);
+ case 'enter':
+ // DocumentDecorations.Instance.onCloseClick(false);
break;
- }
case 'delete':
case 'backspace':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
@@ -173,6 +161,7 @@ export class KeyManager {
case 'arrowright': return this.nudge(1,0, 'nudge right');
case 'arrowup': return this.nudge(0, -1, 'nudge up');
case 'arrowdown': return this.nudge(0, 1, 'nudge down');
+ default:
} // prettier-ignore
return {
@@ -189,17 +178,18 @@ export class KeyManager {
case 'arrowdown': return this.nudge(0, 10, 'nudge down');
case 'u' :
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = undefined)), 'unggroup');
+ UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = undefined}), 'unggroup');
SelectionManager.DeselectAll();
}
break;
case 'g':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
const randomGroup = random(0, 1000);
- UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = randomGroup)), 'group');
+ UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = randomGroup}), 'group');
SelectionManager.DeselectAll();
}
break;
+ default:
} // prettier-ignore
return {
@@ -215,8 +205,9 @@ export class KeyManager {
switch (keyname) {
case 'Æ’':
case 'f':
- const dv = SelectionManager.Views?.[0];
- UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float');
+ UndoManager.RunInBatch(() => SelectionManager.Views?.[0]?.CollectionFreeFormDocumentView?.float(), 'float');
+ break;
+ default:
}
return {
@@ -251,15 +242,19 @@ export class KeyManager {
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);
+ {
+ 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);
+ {
+ const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+ if (trailsBtn) {
+ MainView.Instance.selectMenu(trailsBtn);
+ }
}
break;
case 'f':
@@ -326,6 +321,7 @@ export class KeyManager {
}
preventDefault = false;
break;
+ default:
}
return {
@@ -360,6 +356,7 @@ export class KeyManager {
case 'p':
Doc.ActiveTool = InkTool.Write;
break;
+ default:
}
return {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index edc6b404b..b4fe44733 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -2,11 +2,11 @@ import * as React from 'react';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
-import { ControlPoint, InkData, PointData } from '../../fields/InkField';
+import { ControlPoint, InkData } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
-import { returnFalse, setupMoveUpEvents } from '../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { SelectionManager } from '../util/SelectionManager';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
@@ -14,6 +14,7 @@ import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { SnappingManager } from '../util/SnappingManager';
import { ObservableReactComponent } from './ObservableReactComponent';
+import { PointData } from '../../pen-gestures/GestureTypes';
export interface InkControlProps {
inkDoc: Doc;
@@ -48,7 +49,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
*/
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
- const ptFromScreen = this._props.inkView.ptFromScreen;
+ const { ptFromScreen } = this._props.inkView;
if (ptFromScreen) {
const order = controlIndex % 4;
const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
@@ -60,7 +61,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
@@ -75,9 +76,9 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
- action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ action((moveEv: PointerEvent, doubleTap: boolean | undefined) => {
const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
- if (doubleTap || e.button === 2) {
+ if (doubleTap || moveEv.button === 2) {
if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
if (brokenIndices) brokenIndices.push(controlIndex);
else this._props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
@@ -128,7 +129,9 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
* Changes the current selected control point.
*/
@action
- changeCurrPoint = (i: number) => (InkStrokeProperties.Instance._currentPoint = i);
+ changeCurrPoint = (i: number) => {
+ InkStrokeProperties.Instance._currentPoint = i;
+ };
render() {
// Accessing the current ink's data and extracting all control points.
@@ -176,7 +179,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
};
return (
<svg>
- {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
+ {!nearestScreenPt ? null : <circle key="npt" cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill="#00007777" stroke="#00007777" strokeWidth={0} pointerEvents="none" />}
{sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
</svg>
);
@@ -207,21 +210,21 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
if (this._throttle++ % 2 !== 0) return false;
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 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 v2 = { X: moveEv.clientX - p2.X, Y: moveEv.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.stretchInk(SelectionManager.Views, scaling, p2, v1n, moveEv.shiftKey);
InkStrokeProperties.Instance.rotateInk(SelectionManager.Views, angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
return false;
}),
@@ -243,10 +246,14 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
cy={pt?.Y}
r={this._props.screenSpaceLineWidth * 2}
fill={this._overStart ? '#aaaaaa' : '#99999977'}
- stroke={'#00007777'}
+ stroke="#00007777"
strokeWidth={0}
- onPointerLeave={action(() => (this._overStart = false))}
- onPointerEnter={action(() => (this._overStart = true))}
+ onPointerLeave={action(() => {
+ this._overStart = false;
+ })}
+ onPointerEnter={action(() => {
+ this._overStart = true;
+ })}
onPointerDown={dragFunc}
pointerEvents="all"
/>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 52ea89cde..b3f9f9ea7 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,20 +1,22 @@
import { Bezier } from 'bezier-js';
+import * as _ from 'lodash';
import { action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { Doc, NumListCast, Opt } from '../../fields/Doc';
-import { InkData, InkField, InkTool, PointData } from '../../fields/InkField';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast, NumCast } from '../../fields/Types';
+import { PointData } from '../../pen-gestures/GestureTypes';
import { Point } from '../../pen-gestures/ndollar';
import { DocumentType } from '../documents/DocumentTypes';
-import { FitOneCurve } from '../util/bezierFit';
import { DocumentManager } from '../util/DocumentManager';
import { undoBatch } from '../util/UndoManager';
+import { FitOneCurve } from '../util/bezierFit';
import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
-import * as _ from 'lodash';
export class InkStrokeProperties {
+ // eslint-disable-next-line no-use-before-define
static _Instance: InkStrokeProperties | undefined;
public static get Instance() {
return this._Instance || new InkStrokeProperties();
@@ -28,11 +30,15 @@ export class InkStrokeProperties {
makeObservable(this);
reaction(
() => this._controlButton,
- button => button && (Doc.ActiveTool = InkTool.None)
+ button => {
+ button && (Doc.ActiveTool = InkTool.None);
+ }
);
reaction(
() => Doc.ActiveTool,
- tool => tool !== InkTool.None && (this._controlButton = false)
+ tool => {
+ tool !== InkTool.None && (this._controlButton = false);
+ }
);
}
@@ -46,7 +52,7 @@ export class InkStrokeProperties {
func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number; Y: number }[] | undefined,
requireCurrPoint: boolean = false
) => {
- var appliedFunc = false;
+ let appliedFunc = false;
(strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(
action(inkView => {
if (!requireCurrPoint || this._currentPoint !== -1) {
@@ -85,7 +91,7 @@ export class InkStrokeProperties {
*/
@undoBatch
addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
- this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ this.applyFunction(inkView, (view: DocumentView /* , ink: InkData */) => {
const doc = view.Document;
const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t);
@@ -94,7 +100,9 @@ export class InkStrokeProperties {
// Updating the indices of the control points whose handle tangency has been broken.
doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control)));
- runInAction(() => (this._currentPoint = -1));
+ runInAction(() => {
+ this._currentPoint = -1;
+ });
return controls;
});
@@ -126,8 +134,8 @@ export class InkStrokeProperties {
*/
getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => {
const [m, n] = [C.length, D.length];
- let handleSizeA = Math.sqrt(Math.pow(newControl.X - C[0].X, 2) + Math.pow(newControl.Y - C[0].Y, 2));
- let handleSizeB = Math.sqrt(Math.pow(D[n - 1].X - newControl.X, 2) + Math.pow(D[n - 1].Y - newControl.Y, 2));
+ let handleSizeA = Math.sqrt((newControl.X - C[0].X) ** 2 + (newControl.Y - C[0].Y) ** 2);
+ let handleSizeB = Math.sqrt((D[n - 1].X - newControl.X) ** 2 + (D[n - 1].Y - newControl.Y) ** 2);
// Scaling adjustments to improve the ratio between the magnitudes of the two handle lines.
// (Ensures that the new point added doesn't augment the inital shape of the curve much).
if (handleSizeA < 75 && handleSizeB < 75) {
@@ -167,13 +175,13 @@ export class InkStrokeProperties {
const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
const samples: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < splicedPoints.length / 4; i++) {
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < splicedPoints.length / 4; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === 0) startDir = bez.derivative(0);
if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
+ for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
const pt = bez.compute(t);
samples.push(new Point(pt.x, pt.y));
}
@@ -186,7 +194,9 @@ export class InkStrokeProperties {
}
}
doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
- runInAction(() => (this._currentPoint = -1));
+ runInAction(() => {
+ this._currentPoint = -1;
+ });
return newPoints.length < 4 ? undefined : newPoints;
},
true
@@ -200,7 +210,7 @@ export class InkStrokeProperties {
*/
@undoBatch
rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
- this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
+ this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number /* , inkStrokeWidth: number */) => {
const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt);
return !inkCenterPt
? ink
@@ -247,8 +257,8 @@ export class InkStrokeProperties {
const closed = InkingStroke.IsClosed(ink);
const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
- const cpt_before = ink[controlIndex];
- const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
+ const cptBefore = ink[controlIndex];
+ const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
const newink = origInk.slice();
const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
@@ -256,28 +266,28 @@ export class InkStrokeProperties {
if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1)) return ink.slice();
const samplesLeft: Point[] = [];
const samplesRight: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < nearestSeg / 4 + 1; i++) {
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < nearestSeg / 4 + 1; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0);
if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
- for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
+ for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
samplesLeft.push(new Point(pt.x, pt.y));
}
}
- var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+ let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1);
- for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+ for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
const pt = bez.compute(Math.min(1, t));
samplesRight.push(new Point(pt.x, pt.y));
}
}
- const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
finalCtrls = finalCtrls.concat(rightCtrls);
newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
return newink;
@@ -307,11 +317,12 @@ export class InkStrokeProperties {
});
public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) {
- var distance = Number.MAX_SAFE_INTEGER;
- var nearestT = -1;
- var nearestSeg = -1;
- var nearestPt = { X: 0, Y: 0 };
- for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
+ let distance = Number.MAX_SAFE_INTEGER;
+ let nearestT = -1;
+ let nearestSeg = -1;
+ let nearestPt = { X: 0, Y: 0 };
+ for (let i = 0; i < ctrlPoints.length - 3; i += 4) {
+ // eslint-disable-next-line no-continue
if (excludeSegs?.includes(i)) continue;
const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refInkSpacePt.X, y: refInkSpacePt.Y });
@@ -380,6 +391,7 @@ export class InkStrokeProperties {
const snappedInkPt = doc === inkView.Document ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
if (snappedInkPt) {
+ // eslint-disable-next-line no-param-reassign
snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
}
}
@@ -406,6 +418,7 @@ export class InkStrokeProperties {
inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference);
return inkCopy;
}
+ return undefined;
});
};
@@ -430,7 +443,9 @@ export class InkStrokeProperties {
const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
if (magnitudeA === 0 || magnitudeB === 0) return 0;
// Normalizing the vectors.
+ // eslint-disable-next-line no-param-reassign
vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
+ // eslint-disable-next-line no-param-reassign
vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
return Math.acos(vectorB.X * vectorA.X + vectorB.Y * vectorA.Y);
}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index c20399698..577acc4d1 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -6,18 +6,18 @@ import { HandleLine, HandlePoint, InkData } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents } from '../../Utils';
-import { Transform } from '../util/Transform';
+import { emptyFunction } from '../../Utils';
+import { setupMoveUpEvents } from '../../ClientUtils';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
+
export interface InkHandlesProps {
inkDoc: Doc;
inkView: InkingStroke;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
- ScreenToLocalTransform: () => Transform;
}
@observer
@@ -37,9 +37,9 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
- if (e.altKey) this.onBreakTangent(controlIndex);
+ if (moveEv.altKey) this.onBreakTangent(controlIndex);
const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
@@ -100,11 +100,12 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
}
}
- const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
+ const { screenSpaceLineWidth } = this.props;
return (
<>
{tangentHandles.map((pts, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<svg height="10" width="10" key={`hdl${i}`}>
<circle
cx={pts.X}
@@ -128,12 +129,13 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
x2={x2}
y2={y2}
stroke={Colors.MEDIUM_BLUE}
- strokeDasharray={'1 1'}
+ strokeDasharray="1 1"
strokeWidth={1}
display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
/>
);
return (
+ // eslint-disable-next-line react/no-array-index-key
<svg height="100" width="100" key={`line${i}`}>
{tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
{tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 51baaa23e..35067047b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,18 +23,18 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
import { InkData, InkField } from '../../fields/InkField';
import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils';
import { CognitiveServices } from '../cognitive_services/CognitiveServices';
import { Docs } from '../documents/Documents';
import { InteractionUtils } from '../util/InteractionUtils';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from './DocComponent';
import { Colors } from './global/globalEnums';
import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
@@ -42,9 +42,11 @@ import { InkStrokeProperties } from './InkStrokeProperties';
import { InkTangentHandles } from './InkTangentHandles';
import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './nodes/trails';
+import { PresBox } from './nodes/trails';
import { StyleProp } from './StyleProvider';
+
const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+
@observer
export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
@@ -65,7 +67,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._props.setContentViewBox?.(this);
this._disposers.selfDisper = reaction(
() => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
- selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+ selected => {
+ !selected && (InkStrokeProperties.Instance._controlButton = false);
+ }
);
}
componentWillUnmount() {
@@ -140,7 +144,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
e,
!isEditing
? returnFalse
- : action((e: PointerEvent, down: number[], delta: number[]) => {
+ : action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
@@ -155,7 +159,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
- action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ action((moveEv: PointerEvent, doubleTap: boolean | undefined) => {
if (doubleTap) {
InkStrokeProperties.Instance._controlButton = true;
InkStrokeProperties.Instance._currentPoint = -1;
@@ -167,7 +171,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
}),
isEditing,
isEditing,
- action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+ action(() => {
+ wasSelected && (InkStrokeProperties.Instance._currentPoint = -1);
+ })
);
};
@@ -325,32 +331,34 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
false
)}
<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.ScreenToLocalBoxXf} />
+ <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
</div>
);
};
_subContentView: ViewBoxInterface | undefined;
- setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
- @computed get fillColor() {
+ setSubContentView = (doc: ViewBoxInterface) => {
+ this._subContentView = doc;
+ };
+ @computed get fillColor(): string {
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
}
@computed get strokeColor() {
const { inkData } = this.inkScaledData();
- const fillColor = this.fillColor;
+ const { fillColor } = this;
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();
+ const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY } = this.inkScaledData();
const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
const markerScale = NumCast(this.layoutDoc.stroke_markerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
- const fillColor = this.fillColor;
+ const { fillColor } = this;
// 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(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
@@ -387,12 +395,11 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
1.0,
false,
undefined,
- undefined,
- color === 'transparent' ? highlightColor : undefined
+ undefined
);
const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth));
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) =>
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false): any =>
InteractionUtils.CreatePolyline(
inkData,
inkLeft,
@@ -415,22 +422,29 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
0.0,
false,
downHdlr,
- mask,
- highlightColor
+ mask
);
// bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
// this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
// see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
- const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
+ // const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
const interactions = {
- onPointerLeave: action(() => (this._nearestScrPt = undefined)),
+ onPointerLeave: action(() => {
+ this._nearestScrPt = undefined;
+ }),
onPointerMove: this._props.isSelected() ? this.onPointerMove : undefined,
onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
onContextMenu: () => {
const cm = ContextMenu.Instance;
!Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.dataDoc), icon: 'paint-brush' });
- cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+ cm?.addItem({
+ description: 'Edit Points',
+ event: action(() => {
+ InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+ }),
+ icon: 'paint-brush',
+ });
},
};
return (
@@ -438,10 +452,11 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
<svg
className="inkStroke"
style={{
- transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.rotation ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
// mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
cursor: this._props.isSelected() ? 'default' : undefined,
}}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...interactions}>
{clickableLine(this.onPointerDown, isInkMask)}
{isInkMask ? null : inkLine}
@@ -455,18 +470,19 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
width: NumCast(this.layoutDoc._width),
transform: `scale(${this._props.NativeDimScaling?.() || 1})`,
transformOrigin: 'top left',
- //top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
+ // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
}}>
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setHeight={undefined}
setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
yPadding={10}
xPadding={10}
fieldKey="text"
- //dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
+ // dontRegisterView={true}
+ noSidebar
+ dontScale
isContentActive={this._props.isContentActive}
/>
</div>
@@ -475,37 +491,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
}
-
-export function SetActiveInkWidth(width: string): void {
- !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
-}
-export function SetActiveBezierApprox(bezier: string): void {
- ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
-}
-export function SetActiveInkColor(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
-}
-export function SetActiveIsInkMask(value: boolean) {
- ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
-}
-export function SetActiveInkHideTextLabels(value: boolean) {
- ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
-}
-export function SetActiveFillColor(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
-}
-export function SetActiveArrowStart(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
-}
-export function SetActiveArrowEnd(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
-}
-export function SetActiveArrowScale(value: number) {
- ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
-}
-export function SetActiveDash(dash: string): void {
- !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
-}
export function ActiveInkPen(): Doc {
return Doc.UserDoc();
}
@@ -539,3 +524,34 @@ export function ActiveInkWidth(): number {
export function ActiveInkBezierApprox(): string {
return StrCast(ActiveInkPen()?.activeInkBezier);
}
+
+export function SetActiveInkWidth(width: string): void {
+ !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+ ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveIsInkMask(value: boolean) {
+ ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
+export function SetActiveInkHideTextLabels(value: boolean) {
+ ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
+export function SetActiveFillColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+ !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index e996fc946..81f004010 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
import { observer } from 'mobx-react';
import * as React from 'react';
import './KeyphraseQueryView.scss';
@@ -9,28 +10,21 @@ export interface KP_Props {
@observer
export class KeyphraseQueryView extends React.Component<KP_Props> {
- constructor(props: KP_Props) {
- super(props);
- }
-
render() {
- const kps = this.props.keyphrases.toString();
const keyterms = this.props.keyphrases.split(',');
return (
<div>
<h5>Select queries to send:</h5>
<form>
- {keyterms.map((kp: string) => {
- //return (<p>{"-" + kp}</p>);
- return (
- <p>
- <label>
- <input name="query" type="radio" />
- <span>{kp}</span>
- </label>
- </p>
- );
- })}
+ {keyterms.map((kp: string) => (
+ // return (<p>{"-" + kp}</p>);
+ <p>
+ <label>
+ <input name="query" type="radio" />
+ <span>{kp}</span>
+ </label>
+ </p>
+ ))}
</form>
</div>
);
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ef4b5b4ca..24433cd01 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,10 +1,14 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { InkTool } from '../../fields/InkField';
@@ -42,6 +46,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
*/
public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView && (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore
public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
+ // eslint-disable-next-line no-use-before-define
static Instance: LightboxView;
private _path: {
doc: Opt<Doc>; //
@@ -71,11 +76,18 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
@action
public SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
const lightDoc = this._doc;
- lightDoc && lightDoc !== doc && savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+ lightDoc &&
+ lightDoc !== doc &&
+ savedKeys.forEach(key => {
+ lightDoc[key] = this._savedState[key];
+ });
this._savedState = {};
if (doc) {
- lightDoc !== doc && savedKeys.map(key => (this._savedState[key] = Doc.Get(doc, key, true)));
+ lightDoc !== doc &&
+ savedKeys.forEach(key => {
+ this._savedState[key] = Doc.Get(doc, key, true);
+ });
const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
@@ -106,8 +118,9 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
return true;
}
- public AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) =>
- this.SetLightboxDoc(
+ public AddDocTab = (docs: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => {
+ const doc = (docs instanceof Doc ? [docs] : docs).lastElement();
+ return this.SetLightboxDoc(
doc,
undefined,
[...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort(
@@ -115,6 +128,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
),
layoutTemplate
);
+ };
@action
next = () => {
const lightDoc = this._doc;
@@ -127,7 +141,9 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target });
} else if (!target && this._path.length) {
- savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+ savedKeys.forEach(key => {
+ lightDoc[key] = this._savedState[key];
+ });
this._path.pop();
} else {
this.SetLightboxDoc(target);
@@ -177,8 +193,12 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
this.SetLightboxDoc(undefined);
}
};
- toggleFitWidth = () => this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth);
- togglePen = () => (Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen);
+ toggleFitWidth = () => {
+ this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth);
+ };
+ togglePen = () => {
+ Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+ };
toggleExplore = () => SnappingManager.SetExploreMode(!SnappingManager.ExploreMode);
lightboxDoc = () => this._doc;
@@ -188,33 +208,31 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
lightboxDocTemplate = () => this._layoutTemplate;
future = () => this._future;
- renderNavBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => {
- return (
+ renderNavBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => (
+ <div
+ className="lightboxView-navBtn-frame"
+ style={{
+ display: display ? '' : 'none',
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
+ bottom,
+ }}>
<div
- className="lightboxView-navBtn-frame"
- style={{
- display: display ? '' : 'none',
- left,
- width: bottom !== undefined ? undefined : Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
- bottom,
+ className="lightboxView-navBtn"
+ title={color}
+ style={{ top, color: SettingsManager.userColor, background: undefined }}
+ onClick={e => {
+ e.stopPropagation();
+ click();
}}>
- <div
- className="lightboxView-navBtn"
- title={color}
- style={{ top, color: SettingsManager.userColor, background: undefined }}
- onClick={e => {
- e.stopPropagation();
- click();
- }}>
- <div style={{ height: 10 }}>{color}</div>
- <FontAwesomeIcon icon={icon} size="3x" />
- </div>
+ <div style={{ height: 10 }}>{color}</div>
+ <FontAwesomeIcon icon={icon} size="3x" />
</div>
- );
- };
+ </div>
+ );
render() {
- let downx = 0,
- downy = 0;
+ let downx = 0;
+ let downy = 0;
const toggleBtn = (classname: string, tooltip: string, toggleBackground: any, icon: IconProp, icon2: IconProp | string, onClick: () => void) => (
<div className={classname}>
<Toggle
@@ -239,7 +257,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
downx = e.clientX;
downy = e.clientY;
}}
- onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
+ onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
<div
className="lightboxView-contents"
style={{
@@ -250,10 +268,12 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
background: SettingsManager.userBackgroundColor,
}}>
- <GestureOverlay isActive={true}>
+ <GestureOverlay isActive>
<DocumentView
key={this._doc.title + this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
- ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
+ ref={action((r: DocumentView | null) => {
+ this._docView = r !== null ? r : undefined;
+ })}
Document={this._doc}
PanelWidth={this.lightboxWidth}
PanelHeight={this.lightboxHeight}
@@ -263,7 +283,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
styleProvider={DefaultStyleProvider}
ScreenToLocalTransform={this.lightboxScreenToLocal}
renderDepth={0}
- suppressSetHeight={this._doc._layout_fitWidth ? true : false}
+ suppressSetHeight={!!this._doc._layout_fitWidth}
containerViewPath={returnEmptyDoclist}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
@@ -300,6 +320,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
}
interface LightboxTourBtnProps {
navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => JSX.Element;
+ // eslint-disable-next-line react/no-unused-prop-types
future: () => Opt<Doc[]>;
stepInto: () => void;
lightboxDoc: () => Opt<Doc>;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17c21326d..01f3b032e 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-new */
// if ((module as any).hot) {
// (module as any).hot.accept();
// }
@@ -5,7 +6,7 @@
import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { AssignAllExtensions } from '../../extensions/Extensions';
import { FieldLoader } from '../../fields/FieldLoader';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { PingManager } from '../util/PingManager';
@@ -15,6 +16,7 @@ import { CollectionView } from './collections/CollectionView';
import './global/globalScripts';
import { MainView } from './MainView';
import { BranchingTrailManager } from '../util/BranchingTrailManager';
+
dotenv.config();
AssignAllExtensions();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 58b8d255a..10d423c05 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable node/no-unpublished-import */
import { library } from '@fortawesome/fontawesome-svg-core';
import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
@@ -6,8 +7,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+// eslint-disable-next-line import/no-relative-packages
import '../../../node_modules/browndash-components/dist/styles/global.min.css';
-import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
+import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { DocCast, StrCast } from '../../fields/Types';
@@ -18,10 +21,12 @@ import { Docs } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
+import { UPDATE_SERVER_CACHE } from '../util/LinkManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
@@ -57,7 +62,7 @@ import { AudioBox } from './nodes/AudioBox';
import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
-import { ImageBox, ImageEditorData as ImageEditor } from './nodes/ImageBox';
+import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
@@ -71,11 +76,13 @@ import { PresBox } from './nodes/trails';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { TopBar } from './topbar/TopBar';
-const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+
const _global = (window /* browser */ || global) /* node */ as any;
+const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@observer
export class MainView extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: MainView;
public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -117,8 +124,12 @@ export class MainView extends ObservableReactComponent<{}> {
}
@observable mainDoc: Opt<Doc> = undefined;
@computed private get mainContainer() {
- if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
- DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+ if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail() === 'guest') {
+ DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main =>
+ runInAction(() => {
+ this.mainDoc = main as Doc;
+ })
+ );
return this.mainDoc;
}
return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
@@ -154,6 +165,7 @@ export class MainView extends ObservableReactComponent<{}> {
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
+ // Utils.TraceConsoleLog();
reaction(
// when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
() => SelectionManager.Views.slice(),
@@ -165,7 +177,11 @@ export class MainView extends ObservableReactComponent<{}> {
scriptTag.async = true;
scriptTag.defer = true;
document.body.appendChild(scriptTag);
- document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
+ document.getElementById('root')?.addEventListener('scroll', () =>
+ (ele => {
+ ele.scrollLeft = ele.scrollTop = 0;
+ })(document.getElementById('root')!)
+ );
const ele = document.getElementById('loader');
const prog = document.getElementById('dash-progress');
if (ele && prog) {
@@ -174,7 +190,9 @@ export class MainView extends ObservableReactComponent<{}> {
prog.style.transition = '1s';
prog.style.width = '100%';
}, 0);
- setTimeout(() => (ele.outerHTML = ''), 1000);
+ setTimeout(() => {
+ ele.outerHTML = '';
+ }, 1000);
}
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
@@ -202,7 +220,7 @@ export class MainView extends ObservableReactComponent<{}> {
'text_scrollHeight',
'text_height',
'hidden',
- //'type_collection',
+ // 'type_collection',
'chromeHidden',
'currentFrame',
]); // can play with these fields on someone else's
@@ -530,7 +548,7 @@ export class MainView extends ObservableReactComponent<{}> {
}
private longPressTimer: NodeJS.Timeout | undefined;
- globalPointerClick = action((e: any) => {
+ globalPointerClick = action(() => {
this.longPressTimer && clearTimeout(this.longPressTimer);
DocumentView.LongPress = false;
});
@@ -540,7 +558,9 @@ export class MainView extends ObservableReactComponent<{}> {
globalPointerDown = action((e: PointerEvent) => {
DocumentView.LongPress = false;
this.longPressTimer = setTimeout(
- action(() => (DocumentView.LongPress = true)),
+ action(() => {
+ DocumentView.LongPress = true;
+ }),
1000
);
DocumentManager.removeOverlayViews();
@@ -559,7 +579,7 @@ export class MainView extends ObservableReactComponent<{}> {
});
initEventListeners = () => {
- window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE);
+ window.addEventListener('beforeunload', 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('pointerdown', this.globalPointerDown, true);
@@ -607,8 +627,8 @@ export class MainView extends ObservableReactComponent<{}> {
waitForDoubleClick = () => (SnappingManager.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);
+ addHeaderDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+ removeHeaderDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
@computed get headerBarDocView() {
return (
<div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -625,10 +645,10 @@ export class MainView extends ObservableReactComponent<{}> {
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={true}
+ childHideResizeHandles
childDragAction={dropActionType.move}
- dontRegisterView={true}
- hideResizeHandles={true}
+ dontRegisterView
+ hideResizeHandles
PanelWidth={this.headerBarDocWidth}
PanelHeight={this.headerBarDocHeight}
renderDepth={0}
@@ -664,7 +684,7 @@ export class MainView extends ObservableReactComponent<{}> {
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- suppressSetHeight={true}
+ suppressSetHeight
renderDepth={this._hideUI ? 0 : -1}
/>
</>
@@ -673,7 +693,7 @@ export class MainView extends ObservableReactComponent<{}> {
@computed get dockingContent() {
return (
- <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}>
+ <GestureOverlay isActive={!LightboxView.LightboxDoc}>
<div
key="docking"
className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
@@ -697,9 +717,16 @@ export class MainView extends ObservableReactComponent<{}> {
setupMoveUpEvents(
this,
e,
- action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
- action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)),
- action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+ action(() => {
+ SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX);
+ return !SettingsManager.Instance.propertiesWidth;
+ }),
+ action(() => {
+ SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0);
+ }),
+ action(() => {
+ SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0;
+ }),
false
);
};
@@ -709,7 +736,10 @@ export class MainView extends ObservableReactComponent<{}> {
setupMoveUpEvents(
this,
e,
- action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)),
+ action(ev => {
+ this._leftMenuFlyoutWidth = Math.max(ev.clientX - 58, 0);
+ return false;
+ }),
() => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
this.closeFlyout
);
@@ -717,7 +747,8 @@ export class MainView extends ObservableReactComponent<{}> {
sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
- static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
+ static addDocTabFunc_impl = (docs: Doc | Doc[], location: OpenWhere): boolean => {
+ const doc = (docs instanceof Doc ? [docs] : docs).lastElement();
const whereFields = location.split(':');
const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
@@ -734,7 +765,7 @@ export class MainView extends ObservableReactComponent<{}> {
@computed get flyout() {
return !this._leftMenuFlyoutWidth ? (
- <div key="flyout" className={`mainView-libraryFlyout-out`}>
+ <div key="flyout" className="mainView-libraryFlyout-out">
{this.docButtons}
</div>
) : (
@@ -799,7 +830,7 @@ export class MainView extends ObservableReactComponent<{}> {
if (willOpen) {
switch ((this._panelContent = title)) {
case 'Settings':
- SettingsManager.Instance.open();
+ SettingsManager.Instance.openMgr();
break;
case 'Help':
break;
@@ -837,11 +868,9 @@ export class MainView extends ObservableReactComponent<{}> {
</div>
)}
<div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
- {
- <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
- <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
- </div>
- }
+ <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
+ <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
+ </div>
</div>
</div>
</div>
@@ -878,26 +907,26 @@ export class MainView extends ObservableReactComponent<{}> {
// generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
// interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
- //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
+ // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
this._sidebarContent.proto = DocCast(button.target);
- SettingsManager.Instance.SetLastPressedBtn(button);
+ SettingsManager.Instance.LastPressedBtn = button;
});
closeFlyout = action(() => {
- SettingsManager.Instance.SetLastPressedBtn(undefined);
+ SettingsManager.Instance.LastPressedBtn = undefined;
this._panelContent = 'none';
this._sidebarContent.proto = undefined;
this._leftMenuFlyoutWidth = 0;
});
- remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.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);
+ remButtonDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+ moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
+ addButtonDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
};
@@ -929,7 +958,7 @@ export class MainView extends ObservableReactComponent<{}> {
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
/>
- {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
+ {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : null}
</div>
);
}
@@ -939,12 +968,16 @@ export class MainView extends ObservableReactComponent<{}> {
return !dragPar?.layoutDoc.freeform_snapLines ? null : (
<div className="mainView-snapLines">
<svg style={{ width: '100%', height: '100%' }}>
- {SnappingManager.HorizSnapLines.map((l, i) => (
- <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
- ))}
- {SnappingManager.VertSnapLines.map((l, i) => (
- <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
- ))}
+ {[
+ ...SnappingManager.HorizSnapLines.map((l, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <line key={'horiz' + i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+ )),
+ ...SnappingManager.VertSnapLines.map((l, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <line key={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+ )),
+ ]}
</svg>
</div>
);
@@ -961,13 +994,14 @@ export class MainView extends ObservableReactComponent<{}> {
values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
- 0 0 0 1 0"></feColorMatrix>
- <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
- <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
+ 0 0 0 1 0"
+ />
+ <feGaussianBlur in="color" stdDeviation="4" result="blur" />
+ <feOffset in="blur" dx="0" dy="0" result="offset" />
<feMerge>
- <feMergeNode in="bg"></feMergeNode>
- <feMergeNode in="offset"></feMergeNode>
- <feMergeNode in="SourceGraphic"></feMergeNode>
+ <feMergeNode in="bg" />
+ <feMergeNode in="offset" />
+ <feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
@@ -984,7 +1018,11 @@ export class MainView extends ObservableReactComponent<{}> {
color: SettingsManager.userColor,
background: SettingsManager.userBackgroundColor,
}}
- onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
+ onScroll={() =>
+ (ele => {
+ ele.scrollTop = ele.scrollLeft = 0;
+ })(document.getElementById('root')!)
+ }
ref={r => {
r &&
new _global.ResizeObserver(
@@ -1009,23 +1047,31 @@ export class MainView extends ObservableReactComponent<{}> {
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
{this._hideUI ? null : <TopBar />}
<LinkDescriptionPopup />
- {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
- {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
-
+ {DocButtonState.Instance.LinkEditorDocView ? (
+ <LinkMenu
+ clearLinkEditor={action(() => {
+ DocButtonState.Instance.LinkEditorDocView = undefined;
+ })}
+ docView={DocButtonState.Instance.LinkEditorDocView}
+ />
+ ) : null}
+ {LinkInfo.Instance?.LinkInfo ? (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
+ ) : null}
{((page: string) => {
// prettier-ignore
switch (page) {
- default:
- case 'dashboard': return (<>
+ case 'home': return <DashboardView />;
+ case 'dashboard':
+ default: return (<>
<div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
<CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
</div>
{this.mainDashboardArea}
</> );
- case 'home': return <DashboardView />;
}
})(Doc.ActivePage)}
-
<PreviewCursor />
<TaskCompletionBox />
<ContextMenu />
@@ -1049,15 +1095,19 @@ export class MainView extends ObservableReactComponent<{}> {
}
}
-ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
MainView.Instance.selectMenu(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewPresentation() {
return MainView.Instance.createNewPresentation();
}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function openPresentation(pres: Doc) {
return MainView.Instance.openPresentation(pres);
}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewFolder() {
return MainView.Instance.createNewFolder();
}, 'creates a new folder in myFiles when called');
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index af7f38937..a6dc5c62b 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
import { isDark } from 'browndash-components';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import './MainViewModal.scss';
export interface MainViewOverlayProps {
@@ -11,7 +14,6 @@ export interface MainViewOverlayProps {
dialogueBoxStyle?: React.CSSProperties;
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
- overlayDisplayedOpacity?: number;
closeOnExternalClick?: () => void; // the close method of a MainViewModal, triggered if there is a click on the overlay (closing the modal)
}
@@ -20,7 +22,6 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
render() {
const p = this.props;
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
- const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
return !p.isDisplayed ? null : (
<div
className="mainViewModal-cont"
@@ -43,7 +44,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: isDark(SettingsManager.userColor) ? '#DFDFDF30' : '#32323230',
+ backgroundColor: isDark(SnappingManager.userColor) ? '#DFDFDF30' : '#32323230',
...(p.overlayStyle || {}),
}}
/>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9fa20f642..c4c00e0c3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -14,9 +14,8 @@ import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { AnchorMenu } from './pdf/AnchorMenu';
import { ObservableReactComponent } from './ObservableReactComponent';
-const _global = (window /* browser */ || global) /* node */ as any;
+import { AnchorMenu } from './pdf/AnchorMenu';
export interface MarqueeAnnotatorProps {
Document: Doc;
@@ -96,19 +95,20 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
presentation_zoomText: true,
title: '>' + this.props.Document.title,
});
+ const textRegionAnnoProto = textRegionAnno[DocData];
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxY = -Number.MIN_VALUE;
const annoDocs: Doc[] = [];
- savedAnnoMap.forEach((value: HTMLDivElement[], key: number) =>
- value.map(anno => {
+ savedAnnoMap.forEach((value: HTMLDivElement[]) =>
+ value.forEach(anno => {
const textRegion = new Doc();
textRegion.x = parseInt(anno.style.left ?? '0');
textRegion.y = parseInt(anno.style.top ?? '0');
textRegion._height = parseInt(anno.style.height ?? '0');
textRegion._width = parseInt(anno.style.width ?? '0');
- textRegion.annoTextRegion = textRegionAnno;
+ textRegion.embedContainer = textRegionAnnoProto;
textRegion.backgroundColor = color;
annoDocs.push(textRegion);
anno.remove();
@@ -119,7 +119,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
})
);
- const textRegionAnnoProto = textRegionAnno[DocData];
textRegionAnnoProto.y = Math.max(minY, 0);
textRegionAnnoProto.x = Math.max(minX, 0);
textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
@@ -131,7 +130,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
return textRegionAnno;
};
@action
- highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => {
+ highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
@@ -157,7 +156,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
// 3) localize the unrotated vector by scaling into the marquee container's coordinates
// 4) reattach the vector to the center of the bounding box
getTransformedScreenPt = (down: number[]) => {
- const marqueeContainer = this.props.marqueeContainer;
+ const { marqueeContainer } = this.props;
const containerXf = this.props.isNativeScaled ? this.props.docView().screenToContentsTransform() : this.props.docView().screenToViewTransform();
const boundingRect = marqueeContainer.getBoundingClientRect();
const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
@@ -177,15 +176,15 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
document.addEventListener('pointermove', this.onSelectMove);
document.addEventListener('pointerup', this.onSelectEnd);
- AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnCrop = () => {
if (this.props.anchorMenuCrop) {
UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
}
};
- AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
+ AnchorMenu.Instance.OnClick = undoable(() => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true);
- AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
+ AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]> /* , addAsAnnotation?: boolean */) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
/**
@@ -203,10 +202,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
return target;
};
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
- e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ dragComplete: dragEv => {
+ if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+ dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.props.Document;
+ dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
});
@@ -220,13 +219,14 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
: action((e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
- var cropRegion: Doc | undefined;
+ let cropRegion: Doc | undefined;
+ // eslint-disable-next-line no-return-assign
const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
- const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+ const targetCreator = (/* annotationOn: Doc | undefined */) => this.props.anchorMenuCrop!(cropRegion, false)!;
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.linkDocument) {
- const linkDocData = e.linkDocument[DocData];
+ dragComplete: dragEx => {
+ if (!dragEx.aborted && dragEx.linkDocument) {
+ const linkDocData = dragEx.linkDocument[DocData];
linkDocData.link_relationship = 'cropped image';
linkDocData.title = 'crop: ' + this.props.Document.title;
linkDocData.link_displayLine = false;
@@ -252,7 +252,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
this._width = movLoc.x - this._start.x;
this._height = movLoc.y - this._start.y;
- //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged. if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
+ // e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged. if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
};
onSelectEnd = (e: PointerEvent) => {
@@ -268,7 +268,9 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
// copy the temporary marquee to allow for multiple selections (not currently available though).
const copy = document.createElement('div');
const scale = (this.props.scaling?.() || 1) * NumCast(this.props.Document._freeform_scale, 1);
- ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
+ ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => {
+ copy.style[prop as any] = marqueeStyle[prop as any];
+ });
copy.className = 'marqueeAnnotator-annotationBox';
copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
copy.style.left = parseInt(marqueeStyle.left.toString().replace('px', '')) / scale + 'px';
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
deleted file mode 100644
index 89c3c41f8..000000000
--- a/src/client/views/MetadataEntryMenu.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import * as Autosuggest from 'react-autosuggest';
-import { emptyFunction, emptyPath } from '../../Utils';
-import { Doc, DocListCast, Field } from '../../fields/Doc';
-import { undoBatch } from '../util/UndoManager';
-import './MetadataEntryMenu.scss';
-import { KeyValueBox } from './nodes/KeyValueBox';
-
-export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
-export interface MetadataEntryProps {
- docs: Doc[];
- onError?: () => boolean;
- suggestWithFunction?: boolean;
-}
-
-@observer
-export class MetadataEntryMenu extends React.Component<MetadataEntryProps> {
- @observable private _currentKey: string = '';
- @observable private _currentValue: string = '';
- private _addChildren: boolean = false;
- @observable _allSuggestions: string[] = [];
- _suggestionDispser: IReactionDisposer | undefined;
- private userModified = false;
-
- private autosuggestRef = React.createRef<Autosuggest>();
-
- @action
- onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
- this._currentKey = newValue;
- if (!this.userModified) {
- this.previewValue();
- }
- };
-
- previewValue = async () => {
- let field: Field | undefined | null = null;
- let onProto: boolean = false;
- let value: string | undefined = undefined;
- const docs = this.props.docs;
- for (const doc of docs) {
- const v = await doc[this._currentKey];
- onProto = onProto || !Object.keys(doc).includes(this._currentKey);
- if (field === null) {
- field = v;
- } else if (v !== field) {
- value = 'multiple values';
- }
- }
- if (value === undefined) {
- if (field !== null && field !== undefined) {
- value = (onProto ? '' : '= ') + Field.toScriptString(field);
- } else {
- value = '';
- }
- }
- const s = value;
- runInAction(() => (this._currentValue = s));
- };
-
- @action
- onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._currentValue = e.target.value;
- this.userModified = e.target.value.trim() !== '';
- };
-
- @undoBatch
- @action
- onValueKeyDown = async (e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.stopPropagation();
- const script = KeyValueBox.CompileKVPScript(this._currentValue);
- if (!script) return;
-
- let childSuccess = true;
- if (this._addChildren) {
- for (const document of this.props.docs) {
- const collectionChildren = DocListCast(document.data);
- if (collectionChildren) {
- childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
- }
- }
- }
- const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
- if (!success) {
- if (this.props.onError) {
- if (this.props.onError()) {
- this.clearInputs();
- }
- } else {
- this.clearInputs();
- }
- } else {
- this.clearInputs();
- }
- }
- };
-
- @action
- clearInputs = () => {
- this._currentKey = '';
- this._currentValue = '';
- this.userModified = false;
- if (this.autosuggestRef.current) {
- const input: HTMLInputElement = (this.autosuggestRef.current as any).input;
- input && input.focus();
- }
- };
-
- getKeySuggestions = (value: string) => {
- value = value.toLowerCase();
- const docs = this.props.docs;
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
- };
- getSuggestionValue = (suggestion: string) => suggestion;
-
- renderSuggestion = (suggestion: string) => {
- return null;
- };
- componentDidMount() {
- this._suggestionDispser = reaction(
- () => this._currentKey,
- () => (this._allSuggestions = this.getKeySuggestions(this._currentKey)),
- { fireImmediately: true }
- );
- }
- componentWillUnmount() {
- this._suggestionDispser && this._suggestionDispser();
- }
-
- onClick = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._addChildren = !this._addChildren;
- };
-
- private get considerChildOptions() {
- if (!this.props.docs.every(doc => doc._type_collection !== undefined)) {
- return null;
- }
- return (
- <div style={{ display: 'flex' }}>
- Children:
- <input type="checkbox" onChange={this.onClick}></input>
- </div>
- );
- }
-
- _ref = React.createRef<HTMLInputElement>();
- render() {
- return (
- <div className="metadataEntry-outerDiv" id="metadataEntry-outer" onPointerDown={e => e.stopPropagation()}>
- <div className="metadataEntry-inputArea">
- <div style={{ display: 'flex', flexDirection: 'row' }}>
- <span>Key:</span>
- <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}>
- <Autosuggest
- // @ts-ignore
- inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={emptyPath}
- alwaysRenderSuggestions={false}
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={emptyFunction}
- onSuggestionsClearRequested={emptyFunction}
- ref={this.autosuggestRef}
- />
- </div>
- </div>
- <div style={{ display: 'flex', flexDirection: 'row' }}>
- <span>Value:</span>
- <input className="metadataEntry-input" ref={this._ref} value={this._currentValue} onClick={e => this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
- </div>
- {this.considerChildOptions}
- </div>
- <div className="metadataEntry-keys">
- <ul>
- {this._allSuggestions
- .slice()
- .sort()
- .map(s => (
- <li
- key={s}
- onClick={action(() => {
- this._currentKey = s;
- this.previewValue();
- })}>
- {s}
- </li>
- ))}
- </ul>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 394d1eae2..266411770 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,7 +14,10 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
makeObservable(this);
}
componentDidUpdate(prevProps: Readonly<T>): void {
- Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
- ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
+ Object.keys(prevProps)
+ .filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey])
+ .forEach(action(pkey => {
+ (this._props as any)[pkey] = (this.props as any)[pkey];
+ })); // prettier-ignore
}
}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 15b1f0275..960282a08 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,19 +3,22 @@ import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, emptyFunction } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { NumCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { Transform } from '../util/Transform';
import { LightboxView } from './LightboxView';
import { ObservableReactComponent } from './ObservableReactComponent';
import './OverlayView.scss';
import { DefaultStyleProvider } from './StyleProvider';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type OverlayDisposer = () => void;
@@ -50,14 +53,14 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
this.height = opts.height || 200;
}
- onPointerDown = (_: React.PointerEvent) => {
+ onPointerDown = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.removeEventListener('pointerup', this.onPointerUp);
document.addEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointerup', this.onPointerUp);
};
- onResizerPointerDown = (_: React.PointerEvent) => {
+ onResizerPointerDown = () => {
document.removeEventListener('pointermove', this.onResizerPointerMove);
document.removeEventListener('pointerup', this.onResizerPointerUp);
document.addEventListener('pointermove', this.onResizerPointerMove);
@@ -80,12 +83,12 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
this.height = Math.max(this.height, 30);
};
- onPointerUp = (e: PointerEvent) => {
+ onPointerUp = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.removeEventListener('pointerup', this.onPointerUp);
};
- onResizerPointerUp = (e: PointerEvent) => {
+ onResizerPointerUp = () => {
document.removeEventListener('pointermove', this.onResizerPointerMove);
document.removeEventListener('pointerup', this.onResizerPointerUp);
};
@@ -95,12 +98,12 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
<div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
<div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
{this._props.overlayOptions.title || 'Untitled'}
- <button onClick={this._props.onClick} className="overlayWindow-closeButton">
+ <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
X
</button>
</div>
<div className="overlayWindow-content">{this.props.children}</div>
- <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div>
+ <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
</div>
);
}
@@ -108,6 +111,7 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
@observer
export class OverlayView extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: OverlayView;
@observable.shallow _elements: JSX.Element[] = [];
@@ -118,8 +122,9 @@ export class OverlayView extends ObservableReactComponent<{}> {
OverlayView.Instance = this;
new _global.ResizeObserver(
action((entries: any) => {
- for (const entry of entries) {
- Doc.MyOverlayDocs.forEach(doc => {
+ Array.from(entries).forEach((entry: any) => {
+ Doc.MyOverlayDocs.forEach(docIn => {
+ const doc = docIn;
if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
doc.overlayX = entry.contentRect.width - 10;
}
@@ -127,7 +132,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
doc.overlayY = entry.contentRect.height - 10;
}
});
- }
+ });
})
).observe(window.document.body);
}
@@ -162,12 +167,12 @@ export class OverlayView extends ObservableReactComponent<{}> {
const index = this._elements.indexOf(contents);
if (index !== -1) this._elements.splice(index, 1);
});
- contents = (
+ const wincontents = (
<OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>
{contents}
</OverlayWindow>
);
- this._elements.push(contents);
+ this._elements.push(wincontents);
return remove;
}
@@ -177,15 +182,16 @@ export class OverlayView extends ObservableReactComponent<{}> {
};
docScreenToLocalXf = computedFn(
+ // eslint-disable-next-line prefer-arrow-callback
function docScreenToLocalXf(this: any, doc: Doc) {
return () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1);
- }.bind(this)
+ }
);
@computed get overlayDocs() {
return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
- let offsetx = 0,
- offsety = 0;
+ let offsetx = 0;
+ let offsety = 0;
const dref = React.createRef<HTMLDivElement>();
const onPointerMove = action((e: PointerEvent, down: number[]) => {
if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
@@ -198,9 +204,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
dragData.offset = [-offsetx, -offsety];
dragData.dropAction = dropActionType.move;
dragData.removeDocument = this.removeOverlayDoc;
- dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return dragData.removeDocument!(doc) ? addDocument(doc) : false;
- };
+ dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false);
DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
return true;
}
@@ -227,7 +231,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
PanelHeight={d[Height]}
ScreenToLocalTransform={this.docScreenToLocalXf(d)}
renderDepth={1}
- hideDecorations={true}
+ hideDecorations
isDocumentActive={returnTrue}
isContentActive={returnTrue}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4b7771f27..fddb40624 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,7 +1,7 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { lightOrDark, returnFalse } from '../../Utils';
+import { lightOrDark, returnFalse } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
import { ImageUtils } from '../util/Import & Export/ImageUtils';
@@ -13,6 +13,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
export class PreviewCursor extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static _instance: PreviewCursor;
public static get Instance() {
return PreviewCursor._instance;
@@ -39,7 +40,9 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
paste = async (e: ClipboardEvent) => {
if (this.Visible && e.clipboardData) {
const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
- runInAction(() => (this.Visible = false));
+ runInAction(() => {
+ this.Visible = false;
+ });
// tests for URL and makes web document
const re: any = /^https?:\/\//g;
@@ -88,10 +91,10 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined)), 'paste');
}
}
- //pasting in images
+ // pasting in images
else if (e.clipboardData.getData('text/html') !== '' && e.clipboardData.getData('text/html').includes('<img src=')) {
- const re: any = /<img src="(.*?)"/g;
- const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
+ const regEx: any = /<img src="(.*?)"/g;
+ const arr: any[] = regEx.exec(e.clipboardData.getData('text/html'));
if (newPoint) {
undoBatch(() => {
@@ -122,7 +125,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
@action
onKeyDown = (e: KeyboardEvent) => {
// Mixing events between React and Native is finicky.
- //if not these keys, make a textbox if preview cursor is active!
+ // if not these keys, make a textbox if preview cursor is active!
if (
e.key !== 'Escape' &&
e.key !== 'Backspace' &&
@@ -162,7 +165,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
}
};
- //when focus is lost, this will remove the preview cursor
+ // when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
this.Visible = false;
};
@@ -192,6 +195,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
}
render() {
return !this._clickPoint || !this.Visible ? null : (
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
<div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ color: lightOrDark(this.Doc?.backgroundColor ?? 'white'), transform: `translate(${this._clickPoint[0]}px, ${this._clickPoint[1]}px)` }}>
I
</div>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 517a80d63..d83fea2a5 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/no-unused-class-component-methods */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
@@ -12,7 +15,7 @@ import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtit
import { RxWidth } from 'react-icons/rx';
import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
import { TfiBarChart } from 'react-icons/tfi';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, ScriptCast } from '../../fields/Types';
@@ -32,6 +35,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: PropertiesButtons;
@computed get selectedDoc() {
@@ -44,66 +48,16 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost;
}
- 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.selectedLayoutDoc;
- const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true);
- return !targetDoc ? null : (
- <Toggle
- toggleStatus={BoolCast(targetDoc[property])}
- tooltip={tooltip(BoolCast(targetDoc[property]))}
- text={label(targetDoc?.[property])}
- color={SettingsManager.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.Document, 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.Document;
- //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[Doc.LayoutFieldKey(containerDoc)]);
- //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.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'),
+ on => (!on ? 'SHOW TITLE' : this.selectedDoc?._layout_showTitle === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
'_layout_showTitle',
- on => 'Switch between title styles',
+ () => 'Switch between title styles',
on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ? <MdSubtitles/> :) , //,'text-width', on ? <MdSubtitles/> : <MdSubtitlesOff/>,
(dv, doc) => {
const tdoc = dv?.Document || doc;
const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
- tdoc._layout_showTitle = newtitle ? newtitle : undefined;
+ tdoc._layout_showTitle = newtitle || undefined;
}
);
}
@@ -119,7 +73,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
}
@computed get maskButton() {
- //highlight text while going down and reading through
+ // highlight text while going down and reading through
return this.propertyToggleBtn(
on => (on ? 'PLAIN INK' : 'HIGHLIGHTER MASK'),
'stroke_isInkMask',
@@ -132,10 +86,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get hideImageButton() {
// put in developer -- can trace on top of object and drawing is still there
return this.propertyToggleBtn(
- on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), //'Background',
+ on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), // 'Background',
'_hideImage',
on => (on ? 'Show Image' : 'Show Background'),
- on => (on ? <BiShow /> : <BiHide />) //'portrait'
+ on => (on ? <BiShow /> : <BiHide />) // 'portrait'
);
}
@@ -144,35 +98,35 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'),
'_freeform_useClusters',
on => `${on ? 'Hide' : 'Show'} clusters`,
- on => <FaBraille />
+ () => <FaBraille />
);
}
@computed get panButton() {
return this.propertyToggleBtn(
- on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), //'Lock\xA0View',
+ on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), // 'Lock\xA0View',
'_lockedTransform',
on => `${on ? 'Unlock' : 'Lock'} panning of view`,
- on => (on ? <TbHandStop /> : <TbHandOff />) //'lock'
+ on => (on ? <TbHandStop /> : <TbHandOff />) // 'lock'
);
}
@computed get forceActiveButton() {
- //select text
+ // select text
return this.propertyToggleBtn(
on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'),
'_forceActive',
on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `,
- on => <MdTouchApp /> // 'eye'
+ () => <MdTouchApp /> // 'eye'
);
}
@computed get verticalAlignButton() {
- //select text
+ // select text
return this.propertyToggleBtn(
on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'),
'_layout_centered',
on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `,
- on => <MdTouchApp /> // 'eye'
+ () => <MdTouchApp /> // 'eye'
);
}
@@ -181,9 +135,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'),
'layout_textPainted',
on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
- on => <MdTouchApp />,
+ () => <MdTouchApp />,
(dv, doc) => {
- const on = doc.onPaint ? true : false;
+ const on = !!doc.onPaint;
doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
}
@@ -192,10 +146,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get fitContentButton() {
return this.propertyToggleBtn(
- on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
+ on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), // 'View All',
'_freeform_fitContentsToBox',
on => `${on ? "Don't" : 'Do'} fit content to container visible area`,
- on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) //'object-group'
+ on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) // 'object-group'
);
}
@@ -228,7 +182,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get layout_fitWidthButton() {
return this.propertyToggleBtn(
- on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width',
+ on => (on ? 'SCALED VIEW' : 'READING VIEW'), // 'Fit\xA0Width',
'_layout_fitWidth',
on =>
on
@@ -240,12 +194,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get captionButton() {
return this.propertyToggleBtn(
- //DEVELOPER
- on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption',
+ // DEVELOPER
+ on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), // 'Caption',
'_layout_showCaption',
on => `${on ? 'Hide' : 'Show'} caption footer`,
- on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
- (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined)
+ on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), // 'closed-captioning',
+ (dv, doc) => {
+ (dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined;
+ }
);
}
@@ -256,7 +212,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
'_chromeHidden',
on => `${on ? 'Show' : 'Hide'} editing UI`,
on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
- (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden)
+ (dv, doc) => {
+ (dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden;
+ }
);
}
@@ -265,8 +223,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
'_layout_autoHeight',
- on => `Automatical vertical sizing to show all content`,
- on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
+ () => `Automatical vertical sizing to show all content`,
+ () => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
);
}
@@ -274,8 +232,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'HIDE GRID' : 'DISPLAY GRID'),
'_freeform_backgroundGrid',
- on => `Display background grid in collection`,
- on => (on ? <MdGridOff /> : <MdGridOn />) //'border-all'
+ () => `Display background grid in collection`,
+ on => (on ? <MdGridOff /> : <MdGridOn />) // 'border-all'
);
}
@@ -317,8 +275,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'HIDE SNAP LINES' : 'SHOW SNAP LINES'),
'freeform_snapLines',
- on => `Display snapping lines when objects are dragged`,
- on => <TfiBarChart />, //'th',
+ () => `Display snapping lines when objects are dragged`,
+ () => <TfiBarChart />, // 'th',
undefined
);
}
@@ -361,7 +319,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
handlePerspectiveChange = (e: any) => {
this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
- SelectionManager.Views.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
+ SelectionManager.Views.forEach(docView => {
+ docView.layoutDoc._type_collection = e.target.value;
+ });
};
@computed get onClickVal() {
const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
@@ -369,10 +329,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
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';
+ if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
+ if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
+ if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
+ return 'nothing';
}
@computed
@@ -385,20 +345,18 @@ export class PropertiesButtons extends React.Component<{}, {}> {
['linkOnRight', 'Open Link on Right'],
];
- const items: IListItemProps[] = buttonList.map(value => {
- return {
- text: value[1],
- val: value[1],
- };
- });
+ const items: IListItemProps[] = buttonList.map(value => ({
+ text: value[1],
+ val: value[1],
+ }));
return !this.selectedDoc ? null : (
<Dropdown
- tooltip={'Choose onClick behavior'}
+ tooltip="Choose onClick behavior"
items={items}
- closeOnSelect={true}
+ closeOnSelect
selectedVal={this.onClickVal}
setSelectedVal={val => this.handleOptionChange(val as string)}
- title={'Choose onClick behaviour'}
+ title="Choose onClick behaviour"
color={SettingsManager.userColor}
dropdownType={DropdownType.SELECT}
type={Type.SEC}
@@ -440,16 +398,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
docView.toggleFollowLink(false, false);
docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
break;
+ default:
}
});
};
- @undoBatch
- editOnClickScript = () => {
- if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
- else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
- };
-
@computed
get onClickFlyout() {
const buttonList = [
@@ -473,6 +426,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
case 'nothing': active = !linkButton && this.selectedDoc.onClick === undefined;break;
+ default:
}
return (
<div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
@@ -494,6 +448,36 @@ export class PropertiesButtons extends React.Component<{}, {}> {
</div>
);
}
+ @undoBatch
+ editOnClickScript = () => {
+ if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
+ else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
+ };
+
+ 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.selectedLayoutDoc;
+ const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => {
+ (dv?.layoutDoc || doc)[prop] = !(dv?.layoutDoc || doc)[prop];
+ };
+ return !targetDoc ? null : (
+ <Toggle
+ toggleStatus={BoolCast(targetDoc[property])}
+ tooltip={tooltip(BoolCast(targetDoc[property]))}
+ text={label(targetDoc?.[property])}
+ color={SettingsManager.userColor}
+ icon={icon(targetDoc?.[property] as any)}
+ iconPlacement="left"
+ align="flex-start"
+ fillWidth
+ toggleType={ToggleType.BUTTON}
+ onClick={undoable(() => {
+ if (SelectionManager.Views.length > 1) {
+ SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
+ } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
+ }, property)}
+ />
+ );
+ };
render() {
const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
@@ -505,11 +489,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const isStacking = [CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.NoteTaking].includes(this.selectedDoc?._type_collection as any);
const isFreeForm = this.selectedDoc?._type_collection === CollectionViewType.Freeform;
const isTree = this.selectedDoc?._type_collection === CollectionViewType.Tree;
- const isTabView = this.selectedTabView;
const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => (
<div className="propertiesButtons-button" style={style}>
- {' '}
- {ele}{' '}
+ {ele}
</div>
);
const isNovice = Doc.noviceMode;
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index cf5105efc..be5b7ec1f 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unused-prop-types */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index b8bbde9de..2d04f2fe3 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/anchor-is-valid */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { Cast, StrCast } from '../../fields/Types';
import { DocFocusOrOpen } from '../util/DocumentManager';
@@ -52,9 +55,9 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
.map(doc => ({ col: doc, target }));
}
- getOnClick = (col: Doc, target: Doc) => {
+ getOnClick = (clickCol: Doc) => {
if (!this._props.DocView) return;
- col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col;
+ const col = Doc.IsDataProto(clickCol) ? Doc.MakeDelegate(clickCol) : clickCol;
DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col);
};
@@ -65,7 +68,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
{this._props.hideTitle ? null : <p key="contexts">Contexts:</p>}
{this._docs.map(doc => (
<p key={doc.col[Id] + doc.target[Id]}>
- <a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a>
+ <a onClick={() => this.getOnClick(doc.col)}>{StrCast(doc.col.title)}</a>
</p>
))}
</div>
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 3c9fa1123..b9a587719 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,5 +1,8 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { SettingsManager } from '../util/SettingsManager';
@@ -10,8 +13,6 @@ export interface PropertiesSectionProps {
children?: JSX.Element | string | null;
isOpen: boolean;
setIsOpen: (bool: boolean) => any;
- inSection?: boolean;
- setInSection?: (bool: boolean) => any;
onDoubleClick?: () => void;
}
@@ -21,44 +22,35 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
return SettingsManager.userColor;
}
- @computed get backgroundColor() {
- return SettingsManager.userBackgroundColor;
- }
-
@computed get variantColor() {
return SettingsManager.userVariantColor;
}
- @observable isDouble: boolean = false;
-
render() {
if (this.props.children === undefined || this.props.children === 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.variantColor,
- // 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>
+ return (
+ <div className="propertiesView-section">
+ <div
+ className="propertiesView-sectionTitle"
+ onDoubleClick={action(() => {
+ this.props.onDoubleClick && this.props.onDoubleClick();
+ this.props.setIsOpen(true);
+ })}
+ onClick={action(() => {
+ this.props.setIsOpen(!this.props.isOpen);
+ })}
+ style={{
+ background: this.variantColor,
+ // 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>
- {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
</div>
- );
+ {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
+ </div>
+ );
}
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ae29382c1..5e1c80f0c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable prettier/prettier */
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -8,9 +11,10 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult, SketchPicker } from 'react-color';
-import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
-import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
+import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, Field, FieldType, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
@@ -41,6 +45,7 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { StyleProviderFuncType } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -54,6 +59,7 @@ interface PropertiesViewProps {
export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
+ // eslint-disable-next-line no-use-before-define
public static Instance: PropertiesView | undefined;
constructor(props: any) {
super(props);
@@ -101,14 +107,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@observable openTransform: boolean = true;
@observable openFilters: boolean = false;
- //Pres Trails booleans:
+ // Pres Trails booleans:
@observable openPresTransitions: boolean = true;
@observable openPresProgressivize: boolean = false;
@observable openPresVisibilityAndDuration: boolean = false;
@observable openAddSlide: boolean = false;
@observable openSlideOptions: boolean = false;
- @observable inOptions: boolean = false;
@observable _controlButton: boolean = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -183,35 +188,37 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
.forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))
);
- // prettier-ignore
- Array.from(ids).sort().map(key => {
- const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
- const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
- const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
- const contentElement = (
- <EditableView
- key="editableView"
- contents={displayContents}
- height={13}
- fontSize={10}
- GetValue={() => editableContents}
- SetValue={(value: string) => {
- value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
- return true;
- }}
- />);
- rows.push(
- <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
- <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
- &nbsp;
- {contentElement}
- </div>
- );
- });
+ Array.from(ids)
+ .sort()
+ .forEach(key => {
+ const multiple = Array.from(docs.reduce((set, doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
+ const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
+ const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as FieldType);
+ const contentElement = (
+ <EditableView
+ key="editableView"
+ contents={displayContents}
+ height={13}
+ fontSize={10}
+ GetValue={() => editableContents}
+ SetValue={(value: string) => {
+ value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
+ return true;
+ }}
+ />
+ );
+ rows.push(
+ <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
+ &nbsp;
+ {contentElement}
+ </div>
+ );
+ });
rows.push(
<div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: SettingsManager.userBackgroundColor, textAlign: 'center' }}>
- <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
+ <EditableView key="editableView" oneLine contents="add key:value or #tags" height={13} fontSize={10} GetValue={returnEmptyString} SetValue={this.setKeyValue} />
</div>
);
}
@@ -240,7 +247,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true);
}
return true;
- } else if (value[0] === '#') {
+ }
+ if (value[0] === '#') {
const tags = StrListCast(doc.tags);
if (!tags.includes(value)) {
tags.push(value);
@@ -248,6 +256,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
return true;
}
+ return undefined;
});
return false;
};
@@ -255,38 +264,37 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@observable transform: Transform = Transform.Identity();
getTransform = () => this.transform;
propertiesDocViewRef = (ref: HTMLDivElement) => {
- const observer = new _global.ResizeObserver(
- action((entries: any) => {
+ const resizeObserver = new _global.ResizeObserver(
+ action(() => {
const cliRect = ref.getBoundingClientRect();
this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
})
);
- ref && observer.observe(ref);
+ ref && resizeObserver.observe(ref);
};
@computed get contexts() {
- return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />;
+ return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle addDocTab={this._props.addDocTab} />;
}
@computed get contextCount() {
if (this.selectedDocumentView) {
const target = this.selectedDocumentView.Document;
return Doc.GetEmbeddings(target).length - 1;
- } else {
- return 0;
}
+ return 0;
}
@computed get links() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
- return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
+ return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle addDocTab={this._props.addDocTab} />;
}
@computed get linkCount() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
- var counter = 0;
+ let counter = 0;
- LinkManager.Links(selAnchor).forEach((l, i) => counter++);
+ LinkManager.Links(selAnchor).forEach(() => counter++);
return counter;
}
@@ -308,7 +316,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
fitContentsToBox={returnTrue}
styleProvider={DefaultStyleProvider}
containerViewPath={returnEmptyDoclist}
- dontCenter={'y'}
+ dontCenter="y"
isDocumentActive={returnFalse}
isContentActive={emptyFunction}
NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
@@ -326,13 +334,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
whenChildContentsActiveChanged={emptyFunction}
addDocTab={returnFalse}
pinToPres={emptyFunction}
- dontRegisterView={true}
+ dontRegisterView
/>
</div>
);
- } else {
- return null;
}
+ return null;
}
/**
@@ -352,9 +359,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
if (permission === '-multiple-') dropdownValues.unshift(permission);
return (
<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)}
+ {dropdownValues.map(permissionVal => (
+ <option className="propertiesView-permisssions-select" key={permissionVal} value={permissionVal}>
+ {concat(ReverseHierarchyMap.get(permissionVal)?.image, ' ', permissionVal)}
</option>
))}
</select>
@@ -381,7 +388,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<div className="expansion-button">
<IconButton
- icon={<FontAwesomeIcon icon={'ellipsis-h'} />}
+ icon={<FontAwesomeIcon icon="ellipsis-h" />}
size={Size.XSMALL}
color={SettingsManager.userColor}
onClick={action(() => {
@@ -397,10 +404,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
/**
* @returns a row of the permissions panel
*/
- sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
- if (name == Doc.CurrentUserEmail) {
- name = 'Me';
- }
+ sharingItem(nameIn: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+ const name = nameIn === ClientUtils.CurrentUserEmail() ? 'Me' : nameIn;
return (
<div
className="propertiesView-sharingTable-item"
@@ -415,7 +420,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
{/* {name !== "Me" ? this.notifyIcon : null} */}
<div className="propertiesView-sharingTable-item-permission">
{this.colorACLDropDown(name, admin, permission, false)}
- {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
+ {(permission === 'Owner' && name === 'Me') || showExpansionIcon ? this.expansionIcon : null}
</div>
</div>
);
@@ -425,10 +430,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
* @returns a colored dropdown bar reflective of the permission
*/
colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) {
- var shareImage = ReverseHierarchyMap.get(permission)?.image;
+ const shareImage = ReverseHierarchyMap.get(permission)?.image;
return (
<div>
- <div className={'propertiesView-shareDropDown'}>
+ <div className="propertiesView-shareDropDown">
<div className={`propertiesView-shareDropDown${permission}`}>
<div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div>
</div>
@@ -440,9 +445,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
/**
* Sorting algorithm to sort users.
*/
- sortUsers = (u1: String, u2: String) => {
- return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
- };
+ sortUsers = (u1: String, u2: String) => (u1 > u2 ? -1 : u1 === u2 ? 0 : 1);
/**
* Sorting algorithm to sort groups.
@@ -461,42 +464,42 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views.map(docView => docView.Document);
const target = docs[0];
- const showAdmin = GetEffectiveAcl(target) == 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.selectedLayoutDoc : this.selectedDoc?.[DocData];
// adds each user to usersAdded
SharingManager.Instance.users.forEach(eachUser => {
- var userOnDoc = true;
+ let 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) {
+ if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email !== target.author) {
usersAdded.push(eachUser.user.email);
}
});
// sorts and then adds each user to the table
usersAdded.sort(this.sortUsers);
- usersAdded.map(userEmail => {
+ usersAdded.forEach(userEmail => {
const userKey = `acl-${normalizeEmail(userEmail)}`;
- var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
- var permission = StrCast(aclField);
+ const aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
+ const permission = StrCast(aclField);
individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
});
// adds current user
- var userEmail = Doc.CurrentUserEmail;
- if (userEmail == 'guest') userEmail = 'Guest';
+ let userEmail = ClientUtils.CurrentUserEmail();
+ if (userEmail === 'guest') userEmail = 'Guest';
const userKey = `acl-${normalizeEmail(userEmail)}`;
- if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) {
- var permission;
+ if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail !== target.author) {
+ let 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 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
@@ -509,15 +512,15 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const groupTableEntries: JSX.Element[] = [];
const groupList = GroupManager.Instance?.allGroups || [];
groupList.sort(this.sortGroups);
- groupList.map(group => {
- if (group.title != 'Guest' && this.selectedDoc) {
+ groupList.forEach(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.selectedDoc[groupKey] !== '' && this.selectedDoc[groupKey] !== undefined) {
+ let 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 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));
@@ -531,22 +534,22 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<div>
<div>
- <br></br> Individuals with Access to this Document
+ <br /> Individuals with Access to this Document
</div>
<div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {<div> {individualTableEntries}</div>}
+ <div> {individualTableEntries}</div>
</div>
{groupTableEntries.length > 0 ? (
<div>
<div>
- <br></br> Groups with Access to this Document
+ <br /> Groups with Access to this Document
</div>
<div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {<div> {groupTableEntries}</div>}
+ <div> {groupTableEntries}</div>
</div>
</div>
) : null}
- <br></br> Guest
+ <br /> Guest
<div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div>
</div>
);
@@ -558,7 +561,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@action
- toggleCheckbox = () => (this.layoutFields = !this.layoutFields);
+ toggleCheckbox = () => {
+ this.layoutFields = !this.layoutFields;
+ };
@computed get color() {
return SettingsManager.userColor;
@@ -578,21 +583,17 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
return (
<div>
- <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
+ <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel="Title" fillWidth />
{LinkManager.Instance.currentLinkAnchor ? (
<p className="propertiesView-titleExtender">
- <>
- <b>Anchor:</b>
- {LinkManager.Instance.currentLinkAnchor.title}
- </>
+ <b>Anchor:</b>
+ {StrCast(LinkManager.Instance.currentLinkAnchor.title)}
</p>
) : null}
{this.selectedLink?.title ? (
<p className="propertiesView-titleExtender">
- <>
- <b>Link:</b>
- {this.selectedLink.title}
- </>
+ <b>Link:</b>
+ {StrCast(this.selectedLink.title)}
</p>
) : null}
</div>
@@ -600,9 +601,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get currentType() {
- const documentType = StrCast(this.selectedDoc?.type);
- var currentType: string = documentType;
- var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType);
+ const docType = StrCast(this.selectedDoc?.type) as DocumentType;
+ const colType = StrCast(this.selectedDoc?.type_collection) as CollectionViewType;
+ const capitalizedDocType = ClientUtils.cleanDocumentType(docType, colType);
return (
<div>
@@ -618,7 +619,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get currentComponent() {
- var iconName = StrCast(this.selectedDoc?.systemIcon);
+ const iconName = StrCast(this.selectedDoc?.systemIcon);
if (iconName) {
const Icon = Icons[iconName as keyof typeof Icons];
@@ -650,13 +651,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const ys = ink.map(p => p.Y);
const left = Math.min(...xs);
const top = Math.min(...ys);
- const right = Math.max(...xs);
- const bottom = Math.max(...ys);
_centerPoints.push({ X: left, Y: top });
}
}
- var index = 0;
+ let index = 0;
if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
layout.rotation = NumCast(layout.rotation) + angle;
const inks = Cast(doc.stroke, InkField)?.inkData;
@@ -687,11 +686,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
get controlPointsButton() {
return (
<div className="inking-button">
- <Tooltip title={<div className="dash-tooltip">{'Edit points'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Edit points</div>}>
<div
className="inking-button-points"
style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }}
- onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}>
+ onPointerDown={action(() => {
+ InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+ })}>
<FontAwesomeIcon icon="bezier-curve" size="lg" />
</div>
</Tooltip>
@@ -699,152 +700,129 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
);
}
- inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
- return (
- <div
- className="inputBox"
- style={{
- marginRight: title === 'X:' ? '19px' : '',
- marginLeft: title === '∠:' ? '39px' : '',
- }}>
- <div className="inputBox-title"> {title} </div>
- <input
- className="inputBox-input"
- type="text"
- value={value}
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- onChange={e => setter(e.target.value)}
- onKeyDown={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" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </div>
+ inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => (
+ <div
+ className="inputBox"
+ style={{
+ marginRight: title === 'X:' ? '19px' : '',
+ marginLeft: title === '∠:' ? '39px' : '',
+ }}>
+ <div className="inputBox-title"> {title} </div>
+ <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} onKeyDown={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" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- );
- };
+ </div>
+ );
- inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
- return (
- <div className="inputBox-duo">
- {this.inputBox(key, value, setter, title1)}
- {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
- </div>
- );
- };
+ inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => (
+ <div className="inputBox-duo">
+ {this.inputBox(key, value, setter, title1)}
+ {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
+ </div>
+ );
@action
upDownButtons = (dirs: string, field: string) => {
const selDoc = this.selectedDoc;
if (!selDoc) return;
- //prettier-ignore
+ // prettier-ignore
switch (field) {
case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
- case 'wid':
- const oldWidth = NumCast(selDoc._width);
- const oldHeight = NumCast(selDoc._height);
- const oldX = NumCast(selDoc.x);
- const oldY = NumCast(selDoc.y);
- selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
- if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
- const ink = Cast(selDoc.data, InkField)?.inkData;
- if (ink) {
- const newPoints: { X: number; Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
- const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
- newPoints.push({ X: newX, Y: newY });
+ case 'wid': {
+ const oldWidth = NumCast(selDoc._width);
+ const oldHeight = NumCast(selDoc._height);
+ const oldX = NumCast(selDoc.x);
+ const oldY = NumCast(selDoc.y);
+ selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
+ if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+ const ink = Cast(selDoc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number; Y: number }[] = [];
+ for (let j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
+ const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ selDoc.data = new InkField(newPoints);
}
- selDoc.data = new InkField(newPoints);
}
}
break;
- case 'hgt':
- const oWidth = NumCast(selDoc._width);
- const oHeight = NumCast(selDoc._height);
- const oX = NumCast(selDoc.x);
- const oY = NumCast(selDoc.y);
- selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
- if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
- const ink = Cast(selDoc.data, InkField)?.inkData;
- if (ink) {
- const newPoints: { X: number; Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
- const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
- newPoints.push({ X: newX, Y: newY });
+ case 'hgt': {
+ const oWidth = NumCast(selDoc._width);
+ const oHeight = NumCast(selDoc._height);
+ const oX = NumCast(selDoc.x);
+ const oY = NumCast(selDoc.y);
+ selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
+ if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+ const ink = Cast(selDoc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number; Y: number }[] = [];
+ for (let j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
+ const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ selDoc.data = new InkField(newPoints);
}
- selDoc.data = new InkField(newPoints);
}
}
break;
+ default: { /* empty */ }
}
};
getField(key: string) {
- return Field.toString(this.selectedDoc?.[DocData][key] as Field);
+ return Field.toString(this.selectedDoc?.[DocData][key] as FieldType);
}
- @computed get shapeXps() {
- return NumCast(this.selectedDoc?.x);
- }
- @computed get shapeYps() {
- return NumCast(this.selectedDoc?.y);
- }
- @computed get shapeHgt() {
- return NumCast(this.selectedDoc?._height);
- }
- @computed get shapeWid() {
- return NumCast(this.selectedDoc?._width);
- }
- @computed get strokeThk() {
- return NumCast(this.selectedDoc?.[DocData].stroke_width);
- }
- set shapeXps(value) {
- this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
- }
- set shapeYps(value) {
- this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100);
- }
- set shapeWid(value) {
- this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100);
- }
- set shapeHgt(value) {
- this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
- }
- set strokeThk(value) {
- this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
- }
+ @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore
+ set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeYps() { return NumCast(this.selectedDoc?.y); } // prettier-ignore
+ set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeWid() { return NumCast(this.selectedDoc?._width); } // prettier-ignore
+ set shapeWid(value) { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore
+ set shapeHgt(value) { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get strokeThk(){ return NumCast(this.selectedDoc?.[DocData].stroke_width); } // prettier-ignore
+ set strokeThk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); } // prettier-ignore
@computed get hgtInput() {
return this.inputBoxDuo(
'hgt',
this.shapeHgt,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'),
+ undoable((val: string) => {
+ !Number(val) && (this.shapeHgt = +val);
+ }, 'set height'),
'H:',
'wid',
this.shapeWid,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'),
+ undoable((val: string) => {
+ !isNaN(Number(val)) && (this.shapeWid = +val);
+ }, 'set width'),
'W:'
);
}
@computed get XpsInput() {
+ // prettier-ignore
return this.inputBoxDuo(
'Xps',
this.shapeXps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'),
+ undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val); }, 'set x coord'),
'X:',
'Yps',
this.shapeYps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'),
+ undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val); }, 'set y coord'),
'Y:'
);
}
@@ -854,22 +832,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
private _lastDash: any = '2';
- @computed get colorFil() {
- return StrCast(this.selectedDoc?.[DocData].fillColor);
- }
- @computed get colorStk() {
- return StrCast(this.selectedDoc?.[DocData].color);
- }
- set colorFil(value) {
- this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
- }
- set colorStk(value) {
- this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
- }
+ @computed get colorFil() { return StrCast(this.selectedDoc?.[DocData].fillColor); } // prettier-ignore
+ set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore
+ @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore
+ set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
colorButton(value: string, type: string, setter: () => void) {
return (
- <div className="color-button" key="color" onPointerDown={action(e => setter())}>
+ <div className="color-button" key="color" onPointerDown={action(() => setter())}>
<div
className="color-button-preview"
style={{
@@ -888,7 +858,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<SketchPicker
onChange={undoable(
- action((color: ColorResult) => setter(color.hex)),
+ action((col: ColorResult) => setter(col.hex)),
'set stroke color property'
)}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
@@ -911,10 +881,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get fillPicker() {
- return this.colorPicker(this.colorFil, (color: string) => (this.colorFil = color));
+ return this.colorPicker(this.colorFil, (color: string) => { this.colorFil = color; }); // prettier-ignore
}
@computed get linePicker() {
- return this.colorPicker(this.colorStk, (color: string) => (this.colorStk = color));
+ return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore
}
@computed get strokeAndFill() {
@@ -936,60 +906,41 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
);
}
- @computed get dashdStk() {
- return this.selectedDoc?.stroke_dash || '';
- }
- @computed get widthStk() {
- return this.getField('stroke_width') || '1';
- }
- @computed get markScal() {
- return Number(this.getField('stroke_markerScale') || '1');
- }
- @computed get markHead() {
- return this.getField('stroke_startMarker') || '';
- }
- @computed get markTail() {
- return this.getField('stroke_endMarker') || '';
- }
+ @computed get dashdStk() { return this.selectedDoc?.stroke_dash || ''; } // prettier-ignore
set dashdStk(value) {
value && (this._lastDash = value);
this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
}
- set markScal(value) {
- this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
- }
+ @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore
set widthStk(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
}
+ @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore
+ set markScal(value) {
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
+ }
+ @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore
set markHead(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
}
+ @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore
set markTail(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
}
- @computed get stkInput() {
- return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val));
- }
- @computed get markScaleInput() {
- return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val)));
- }
-
- regInput = (key: string, value: any, setter: (val: string) => {}) => {
- return (
- <div className="inputBox">
- <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} 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" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </div>
+ regInput = (key: string, value: any, setter: (val: string) => {}) => (
+ <div className="inputBox">
+ <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} 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" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- );
- };
+ </div>
+ );
@action
CloseAll = () => {
@@ -1005,18 +956,55 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get widthAndDash() {
return (
+ // prettier-ignore
<div className="widthAndDash">
- <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
- <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
+ <div className="width">
+ {this.getNumber(
+ 'Thickness',
+ '',
+ 0,
+ Math.max(50, this.strokeThk),
+ this.strokeThk,
+ (val: number) => { !isNaN(val) && (this.strokeThk = val); },
+ 50,
+ 1
+ )}
+ </div>
+ <div className="width">
+ {this.getNumber(
+ 'Arrow Scale',
+ '',
+ 0,
+ Math.max(10, this.markScal),
+ this.markScal,
+ (val: number) => { !isNaN(val) && (this.markScal = val); },
+ 10,
+ 1
+ )}
+ </div>
<div className="arrows">
<div className="arrows-head">
<div className="arrows-head-title">Arrow Head: </div>
- <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
+ <input
+ key="markHead"
+ className="arrows-head-input"
+ type="checkbox"
+ checked={this.markHead !== ''}
+ onChange={undoBatch(action(() => { this.markHead = this.markHead ? '' : 'arrow'; }))}
+ />
</div>
<div className="arrows-tail">
<div className="arrows-tail-title">Arrow End: </div>
- <input key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} onChange={undoBatch(action(() => (this.markTail = this.markTail ? '' : 'arrow')))} />
+ <input
+ key="markTail"
+ className="arrows-tail-input"
+ type="checkbox"
+ checked={this.markTail !== ''}
+ onChange={undoBatch(
+ action(() => { this.markTail = this.markTail ? '' : 'arrow'; })
+ )}
+ />
</div>
</div>
<div className="dashed">
@@ -1045,50 +1033,53 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
setFinalNumber = () => {
this._sliderBatch?.end();
};
- getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => {
- return (
- <div key={label + this.selectedDoc?.title}>
- <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
- <Slider
- key={label}
- onPointerDown={e => (this._sliderBatch = UndoManager.StartBatch('slider ' + label))}
- multithumb={false}
- color={this.color}
- size={Size.XSMALL}
- min={min}
- max={max}
- autorangeMinVal={autorangeMinVal}
- autorange={autorange}
- number={number}
- unit={unit}
- decimals={1}
- setFinalNumber={this.setFinalNumber}
- setNumber={setNumber}
- fillWidth
- />
- </div>
- );
- };
+ getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => (
+ <div key={label + (this.selectedDoc?.title ?? '')}>
+ <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
+ <Slider
+ key={label}
+ onPointerDown={() => {
+ this._sliderBatch = UndoManager.StartBatch('slider ' + label);
+ }}
+ multithumb={false}
+ color={this.color}
+ size={Size.XSMALL}
+ min={min}
+ max={max}
+ autorangeMinVal={autorangeMinVal}
+ autorange={autorange}
+ number={number}
+ unit={unit}
+ decimals={1}
+ setFinalNumber={this.setFinalNumber}
+ setNumber={setNumber}
+ fillWidth
+ />
+ </div>
+ );
+ setVal = (func: (doc: Doc, val: number) => void) => (val: number) => this.selectedDoc && !isNaN(val) && func(this.selectedDoc, val);
@computed get transformEditor() {
return (
+ // prettier-ignore
<div className="transform-editor">
- {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
- {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
- {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))}
- {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
+ {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), this.setVal((doc: Doc, val: number) => { doc.gridGap = val; })) }
+ {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), this.setVal((doc: Doc, val: number) => { doc.xMargin = val; })) }
+ {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), this.setVal((doc: Doc, val: number) => { doc.yMargin = val; })) }
+ {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), this.setVal((doc: Doc, val: number) => { doc.xPadding = doc.yPadding = val; })) }
{this.isInk ? this.controlPointsButton : null}
- {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
- {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
- {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)}
- {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)}
+ {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, this.setVal((doc: Doc, val:number) => {this.shapeWid = val}), 1000, 1)}
+ {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, this.setVal((doc: Doc, val:number) => {this.shapeHgt = val}), 1000, 1)}
+ {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, this.setVal((doc: Doc, val:number) => {this.shapeXps = val}), 1000)}
+ {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, this.setVal((doc: Doc, val:number) => {this.shapeYps = val}), 1000)}
</div>
);
}
@computed get optionsSubMenu() {
return (
- <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}>
+ // prettier-ignore
+ <PropertiesSection title="Options" isOpen={this.openOptions} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
<PropertiesButtons />
</PropertiesSection>
);
@@ -1096,12 +1087,23 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get sharingSubMenu() {
return (
- <PropertiesSection title="Sharing and Permissions" isOpen={this.openSharing} setIsOpen={bool => (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}>
+ // prettier-ignore
+ <PropertiesSection
+ title="Sharing and Permissions"
+ isOpen={this.openSharing}
+ setIsOpen={bool => { this.openSharing = bool; }}
+ onDoubleClick={this.CloseAll}>
<>
{/* <div className="propertiesView-buttonContainer"> */}
<div className="propertiesView-acls-checkbox">
Layout Permissions
- <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+ <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!)}>
@@ -1141,7 +1143,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get filtersSubMenu() {
return (
- <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
+ // prettier-ignore
+ <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => { this.openFilters = bool; }} onDoubleClick={this.CloseAll}>
<div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
<FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
@@ -1151,11 +1154,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get inkSubMenu() {
return (
+ // prettier-ignore
<>
- <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}>
{this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null}
</PropertiesSection>
- <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}>
{this.transformEditor}
</PropertiesSection>
</>
@@ -1164,7 +1168,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get fieldsSubMenu() {
return (
- <PropertiesSection title="Fields & Tags" isOpen={this.openFields} setIsOpen={bool => (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection
+ title="Fields & Tags"
+ isOpen={this.openFields}
+ setIsOpen={bool => {
+ this.openFields = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
<div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>
</PropertiesSection>
);
@@ -1172,7 +1182,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get contextsSubMenu() {
return (
- <PropertiesSection title="Other Contexts" isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection
+ title="Other Contexts"
+ isOpen={this.openContexts}
+ setIsOpen={bool => {
+ this.openContexts = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
</PropertiesSection>
);
@@ -1180,7 +1196,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get linksSubMenu() {
return (
- <PropertiesSection title="Linked To" isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll}>
+ <PropertiesSection
+ title="Linked To"
+ isOpen={this.openLinks}
+ setIsOpen={bool => {
+ this.openLinks = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.linkCount > 0 ? this.links : 'There are no current links.'}
</PropertiesSection>
);
@@ -1188,14 +1210,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get layoutSubMenu() {
return (
- <PropertiesSection title="Layout" isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={this.CloseAll}>
+ <PropertiesSection
+ title="Layout"
+ isOpen={this.openLayout}
+ setIsOpen={bool => {
+ this.openLayout = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.layoutPreview}
</PropertiesSection>
);
}
@computed get description() {
- return Field.toString(this.selectedLink?.link_description as any as Field);
+ return Field.toString(this.selectedLink?.link_description as any as FieldType);
}
@computed get relationship() {
return StrCast(this.selectedLink?.link_relationship);
@@ -1250,12 +1278,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
// if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
} else if (linkRelationshipList && value !== prevRelationship) {
const index = linkRelationshipList.indexOf(value);
- //increment size of new relationship size
+ // increment size of new relationship size
if (index !== -1 && index < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[index];
linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
}
- //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
+ // decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
if (linkRelationshipList.includes(prevRelationship)) {
const pindex = linkRelationshipList.indexOf(prevRelationship);
if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
@@ -1265,21 +1293,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
}
this.relationshipButtonColor = 'rgb(62, 133, 55)';
- setTimeout(
- action(() => (this.relationshipButtonColor = '')),
- 750
- );
+ setTimeout(action(() => { this.relationshipButtonColor = ''; }), 750); // prettier-ignore
return true;
}
+ return undefined;
});
- changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior');
+ changeFollowBehavior = undoable((loc: Opt<string>) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc);
+ }, 'change follow behavior');
@undoBatch
- changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior));
+ changeAnimationBehavior = action((behavior: string) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior);
+ });
@undoBatch
- changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect));
+ changeEffectDirection = action((effect: PresEffectDirection) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect);
+ });
animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
const lanch = this.sourceAnchor;
@@ -1300,7 +1332,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
document.getElementById('link_description_input')?.blur();
};
- onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+ onDescriptionKey = () => {
// if (e.key === 'Enter') {
// this.setDescripValue(this.description);
// document.getElementById('link_description_input')?.blur();
@@ -1320,7 +1352,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
toggleLinkProp = (e: React.PointerEvent, prop: string) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]))));
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(action(() => { this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]); })) // prettier-ignore
+ );
};
@computed get destinationAnchor() {
@@ -1343,12 +1381,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
e,
returnFalse,
emptyFunction,
- undoBatch(
- action(() => {
- anchor[prop] = anchor[prop] === value ? ovalue : value;
- this.selectedDoc && cb(anchor[prop]);
- })
- )
+ undoBatch(action(() => {
+ anchor[prop] = anchor[prop] === value ? ovalue : value;
+ this.selectedDoc && cb(anchor[prop]);
+ })) // prettier-ignore
);
};
@@ -1357,7 +1393,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<input
style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- autoComplete={'off'}
+ autoComplete="off"
id="link_relationship_input"
value={StrCast(this.selectedLink?.link_relationship)}
onKeyDown={this.onRelationshipKey}
@@ -1459,11 +1495,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
'10',
NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000,
true,
- (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)),
+ (val: string) =>
+ PresBox.SetTransitionTime(val, (timeInMS: number) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS);
+ }),
indent
)}{' '}
<div
- className={'slider-headers'}
+ className="slider-headers"
style={{
display: 'grid',
justifyContent: 'space-between',
@@ -1478,111 +1517,147 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
</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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>Play Target Video</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', 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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly={true} type="number" value={zoom} />
+ <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly 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'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
- <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>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="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'}
+ className="slider-headers"
style={{
display: !targZoom ? 'none' : 'grid',
justifyContent: 'space-between',
@@ -1594,7 +1669,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}}>
<div className="slider-text">0%</div>
<div className="slider-text">100%</div>
- </div>{' '}
+ </div>
</div>
)}
</>
@@ -1622,128 +1697,136 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
</div>
</div>
);
- } else {
- if (this.selectedDoc && !this.isPres) {
- return (
- <div
- className="propertiesView"
- style={{
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
- width: this._props.width,
- minWidth: this._props.width,
- }}>
- <div className="propertiesView-propAndInfoGrouping">
- <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
- Properties
- <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
- <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
- </div>
+ }
+ if (this.selectedDoc && !this.isPres) {
+ return (
+ <div
+ className="propertiesView"
+ style={{
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
+ width: this._props.width,
+ minWidth: this._props.width,
+ }}>
+ <div className="propertiesView-propAndInfoGrouping">
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+ Properties
+ <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
+ <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
</div>
</div>
+ </div>
- <div className="propertiesView-name">{this.editableTitle}</div>
- <div className="propertiesView-type"> {this.currentType} </div>
- {this.fieldsSubMenu}
- {this.optionsSubMenu}
- {this.linksSubMenu}
- {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
- {this.inkSubMenu}
- {this.contextsSubMenu}
- {isNovice ? null : this.sharingSubMenu}
- {this.filtersSubMenu}
- {isNovice ? null : this.layoutSubMenu}
+ <div className="propertiesView-name">{this.editableTitle}</div>
+ <div className="propertiesView-type"> {this.currentType} </div>
+ {this.fieldsSubMenu}
+ {this.optionsSubMenu}
+ {this.linksSubMenu}
+ {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
+ {this.inkSubMenu}
+ {this.contextsSubMenu}
+ {isNovice ? null : this.sharingSubMenu}
+ {this.filtersSubMenu}
+ {isNovice ? null : this.layoutSubMenu}
+ </div>
+ );
+ }
+ if (this.isPres && PresBox.Instance) {
+ const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
+ const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+ ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+ : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
+ return (
+ <div className="propertiesView" style={{ width: this._props.width }}>
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+ Presentation
</div>
- );
- }
- if (this.isPres && PresBox.Instance) {
- const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
- const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
- ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
- : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
- return (
- <div className="propertiesView" style={{ width: this._props.width }}>
- <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
- Presentation
- </div>
- <div className="propertiesView-name" style={{ borderBottom: 0 }}>
- {this.editableTitle}
- <div className="propertiesView-presSelected">
- <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
- <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
- </div>
+ <div className="propertiesView-name" style={{ borderBottom: 0 }}>
+ {this.editableTitle}
+ <div className="propertiesView-presSelected">
+ <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
+ <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
</div>
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ </div>
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresTransitions = !this.openPresTransitions;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Transitions
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
</div>
- )}
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibility
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Visibility
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
</div>
- )}
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Progressivize
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresProgressivize = !this.openPresProgressivize;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Progressivize
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
</div>
- )}
- {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.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-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openSlideOptions = !this.openSlideOptions;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.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-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
</div>
- )}
- </div>
- );
- }
+ {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
+ </div>
+ )}
+ </div>
+ );
}
+ return null;
}
}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 623201ed1..8de359e07 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,17 +1,18 @@
+/* eslint-disable react/require-default-props */
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { emptyFunction } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
-import { emptyFunction } from '../../Utils';
import { DragManager } from '../util/DragManager';
import { CompileScript } from '../util/Scripting';
import { EditableView } from './EditableView';
-import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import './ScriptBox.scss';
-import { DocData } from '../../fields/DocSymbols';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
export interface ScriptBoxProps {
onSave: (text: string, onError: (error: string) => void) => void;
@@ -25,6 +26,7 @@ export interface ScriptBoxProps {
export class ScriptBox extends React.Component<ScriptBoxProps> {
@observable
private _scriptText: string;
+ overlayDisposer?: () => void;
constructor(props: ScriptBoxProps) {
super(props);
@@ -42,7 +44,6 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
console.log('ScriptBox: ' + error);
};
- overlayDisposer?: () => void;
onFocus = () => {
this.overlayDisposer?.();
this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
@@ -53,21 +54,35 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
};
render() {
- let onFocus: Opt<() => void> = undefined,
- onBlur: Opt<() => void> = undefined;
+ let onFocus: Opt<() => void>;
+ let onBlur: Opt<() => void>;
if (this.props.showDocumentIcons) {
onFocus = this.onFocus;
onBlur = this.onBlur;
}
- const params = <EditableView contents={''} display={'block'} maxHeight={72} height={35} fontSize={28} GetValue={() => ''} SetValue={(value: string) => (this.props.setParams?.(value.split(' ').filter(s => s !== ' ')) ? true : true)} />;
+ const params = (
+ <EditableView
+ contents=""
+ display="block"
+ maxHeight={72}
+ height={35}
+ fontSize={28}
+ GetValue={() => ''}
+ SetValue={(value: string) => {
+ this.props.setParams?.(value.split(' ').filter(s => s !== ' '));
+ return true;
+ }}
+ />
+ );
return (
<div className="scriptBox-outerDiv">
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
- <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
+ <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur} />
<div style={{ background: 'beige' }}>{params}</div>
</div>
<div className="scriptBox-toolbar">
<button
+ type="button"
onClick={e => {
this.props.onSave(this._scriptText, this.onError);
e.stopPropagation();
@@ -75,6 +90,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
Save
</button>
<button
+ type="button"
onClick={e => {
this.props.onCancel && this.props.onCancel();
e.stopPropagation();
@@ -85,11 +101,12 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
</div>
);
}
- //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
+ // let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
+ // eslint-disable-next-line react/sort-comp
public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }, defaultScript?: ScriptField) {
let overlayDisposer: () => void = emptyFunction;
const script = ScriptCast(doc[fieldKey]) || defaultScript;
- let originalText: string | undefined = undefined;
+ let originalText: string | undefined;
if (script) {
originalText = script.script.originalScript;
}
@@ -124,7 +141,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
div.style.display = 'inline-block';
div.style.transform = `translate(${clientX}px, ${clientY}px)`;
div.innerHTML = 'button';
- params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
+ params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, () => {}, clientX, clientY);
doc[DocData][fieldKey] = new ScriptField(script);
overlayDisposer();
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index acf0ecff4..ba2e22b3b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
@@ -12,6 +15,36 @@ import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
+interface replValueProps {
+ scrollToBottom: () => void;
+ value: any;
+ name?: string;
+}
+@observer
+export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ render() {
+ const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
+ const title = (name: string) => (
+ <>
+ {this._props.name ? <b>{this._props.name} : </b> : <> </>}
+ {name}
+ </>
+ );
+ if (typeof val === 'object') {
+ // eslint-disable-next-line no-use-before-define
+ return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
+ }
+ if (typeof val === 'function') {
+ return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
+ }
+ return <div className="scriptingObject-leaf">{title(String(val))}</div>;
+ }
+}
interface ReplProps {
scrollToBottom: () => void;
value: { [key: string]: any };
@@ -37,7 +70,7 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
const name = (proto && proto.constructor && proto.constructor.name) || String(val);
const title = (
<>
- {this.props.name ? <b>{this._props.name} : </b> : <></>}
+ {this.props.name ? <b>{this._props.name} : </b> : null}
{name}
</>
);
@@ -50,53 +83,23 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
{title} (+{Object.keys(val).length})
</div>
);
- } else {
- return (
- <div className="scriptingObject-open">
- <div>
- <span onClick={this.toggle} className="scriptingObject-icon">
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </span>
- {title}
- </div>
- <div className="scriptingObject-fields">
- {Object.keys(val).map(key => (
- <ScriptingValueDisplay {...this._props} name={key} />
- ))}
- </div>
- </div>
- );
}
- }
-}
-
-interface replValueProps {
- scrollToBottom: () => void;
- value: any;
- name?: string;
-}
-@observer
-export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
-
- render() {
- const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
- const title = (name: string) => (
- <>
- {this._props.name ? <b>{this._props.name} : </b> : <> </>}
- {name}
- </>
+ return (
+ <div className="scriptingObject-open">
+ <div>
+ <span onClick={this.toggle} className="scriptingObject-icon">
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </span>
+ {title}
+ </div>
+ <div className="scriptingObject-fields">
+ {Object.keys(val).map(key => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <ScriptingValueDisplay {...this._props} name={key} />
+ ))}
+ </div>
+ </div>
);
- if (typeof val === 'object') {
- return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
- } else if (typeof val === 'function') {
- const name = '[Function]';
- return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
- }
- return <div className="scriptingObject-leaf">{title(String(val))}</div>;
}
}
@@ -119,47 +122,45 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
private args: any = {};
- getTransformer = (): Transformer => {
- return {
- transformer: context => {
- const knownVars: { [name: string]: number } = {};
- const usedDocuments: number[] = [];
- ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1));
- return root => {
- function visit(node: ts.Node) {
- let skip = false;
- if (ts.isIdentifier(node)) {
- if (ts.isParameter(node.parent)) {
- skip = true;
- knownVars[node.text] = 1;
- }
+ getTransformer = (): Transformer => ({
+ transformer: context => {
+ const knownVars: { [name: string]: number } = {};
+ const usedDocuments: number[] = [];
+ ScriptingGlobals.getGlobals().forEach((global: any) => {
+ knownVars[global] = 1;
+ });
+ return root => {
+ function visit(nodeIn: ts.Node) {
+ if (ts.isIdentifier(nodeIn)) {
+ if (ts.isParameter(nodeIn.parent)) {
+ knownVars[nodeIn.text] = 1;
}
- node = ts.visitEachChild(node, visit, context);
+ }
+ const node = ts.visitEachChild(nodeIn, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (ts.isParameter(node.parent)) {
- // delete knownVars[node.text];
- } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
- const match = node.text.match(/d([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- usedDocuments.push(m);
- } else {
- return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
- // ts.createPropertyAccess(ts.createIdentifier('args'), node);
- }
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (ts.isParameter(node.parent)) {
+ // delete knownVars[node.text];
+ } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+ const match = node.text.match(/d([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ usedDocuments.push(m);
+ } else {
+ return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+ // ts.createPropertyAccess(ts.createIdentifier('args'), node);
}
}
-
- return node;
}
- return ts.visitNode(root, visit);
- };
- },
- };
- };
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
+ },
+ });
@action
onKeyDown = (e: React.KeyboardEvent) => {
@@ -168,14 +169,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
case 'Enter': {
e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document));
+ DocumentManager.Instance.DocumentViews.forEach((dv, i) => {
+ docGlobals[`d${i}`] = dv.Document;
+ });
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
- const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
+ const result = undoable(() => script.run({ args: this.args }, () => 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);
@@ -260,18 +263,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
return (
<div className="scriptingRepl-outerContainer">
<div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
- {this.commands.map(({ command, result }, i) => {
- return (
- <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
- <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
- {command || <br />}
- </div>
- <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
- {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}
- </div>
+ {this.commands.map(({ command, result }, i) => (
+ <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
+ <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
+ {command || <br />}
+ </div>
+ <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
+ <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />
</div>
- );
- })}
+ </div>
+ ))}
</div>
<input
className="scriptingRepl-commandInput"
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3ad3c92da..6195dcde8 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,8 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnAll, returnFalse, returnOne, returnZero } from '../../Utils';
-import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc';
+import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
@@ -18,7 +22,6 @@ import { StyleProp } from './StyleProvider';
import { CollectionStackingView } from './collections/CollectionStackingView';
import { FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { DocData } from '../../fields/DocSymbols';
interface ExtraProps {
fieldKey: string;
@@ -44,7 +47,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
_stackRef = React.createRef<CollectionStackingView>();
@computed get allMetadata() {
- const keys = new Map<string, FieldResult<Field>>();
+ const keys = new Map<string, FieldResult<FieldType>>();
DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
SearchUtil.documentKeys(doc)
.filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
@@ -95,7 +98,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
return {
type: 'dashField',
attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
- marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
+ marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: ClientUtils.CurrentUserEmail(), modified: 0 } }],
};
});
@@ -196,7 +199,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
</div>
);
};
- const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
+ const renderMeta = (tag: string) => {
const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
return (
<div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
@@ -227,13 +230,12 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
{this.allHashtags.map(renderTag)}
- {Array.from(this.allMetadata.keys())
- .sort()
- .map(key => renderMeta(key, this.allMetadata.get(key)))}
+ {Array.from(this.allMetadata.keys()).sort().map(renderMeta)}
</div>
<div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
<CollectionStackingView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -247,11 +249,11 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
isAnnotationOverlay={false}
select={emptyFunction}
NativeDimScaling={returnOne}
- //childlayout_showTitle={this.layout_showTitle}
+ // childlayout_showTitle={this.layout_showTitle}
isAnyChildContentActive={returnFalse}
childDocumentsActive={this._props.isContentActive}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- childHideDecorationTitle={true}
+ childHideDecorationTitle
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index dcec2fe3d..3697aa010 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
@@ -7,10 +10,10 @@ import { extname } from 'path';
import * as React from 'react';
import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
+import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { DashColor, lightOrDark, Utils } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
import { IsFollowLinkScript } from '../util/LinkFollower';
@@ -26,6 +29,7 @@ import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PropertiesView } from './PropertiesView';
import './StyleProvider.scss';
+import { ScriptField } from '../../fields/ScriptField';
export enum StyleProp {
TreeViewIcon = 'treeView_Icon',
@@ -45,6 +49,7 @@ export enum StyleProp {
TitleHeight = 'titleHeight', // Height of Title area
ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
BorderPath = 'customBorder', // border path for document view
+ FontColor = 'fontColor', // color o tet
FontSize = 'fontSize', // size of text font
FontFamily = 'fontFamily', // font family of text
FontWeight = 'fontWeight', // font weight of text
@@ -70,6 +75,19 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
ScriptCast(doc?.onPaint)?.script.run(scriptProps);
}
+export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
+ const style: { [key: string]: any } = {};
+ const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+ const replacer = (match: any, expr: string) =>
+ // bcz: this executes a script to convert a property expression string: { script } into a value
+ ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
+ divKeys.forEach((prop: string) => {
+ const p = (props as any)[prop];
+ typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+ });
+ return style;
+}
+
export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${
pw * (1 - inset)
@@ -90,20 +108,40 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
const isAnnotated = property.includes(':annotated');
const layoutDoc = doc ? Doc.Layout(doc) : doc;
- const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
const isOpen = property.includes(':open');
- const isEmpty = property.includes(':empty');
const boxBackground = property.includes(':box');
- const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
+ const {
+ fieldKey: fieldKeyProp,
+ styleProvider,
+ pointerEvents,
+ isGroupActive,
+ isDocumentActive,
+ containerViewPath,
+ childFilters,
+ hideCaptions,
+ // eslint-disable-next-line camelcase
+ layout_showTitle,
+ childFiltersByRanges,
+ renderDepth,
+ docViewPath,
+ DocumentView,
+ LayoutTemplateString,
+ disableBrushing,
+ NativeDimScaling,
+ PanelWidth,
+ PanelHeight,
+ } = props || {}; // extract props that are not shared between fieldView and documentView props.
+ const fieldKey = fieldKeyProp ? fieldKeyProp + '_' : isCaption ? 'caption_' : '';
+ const isInk = () => layoutDoc?._layout_isSvg && !LayoutTemplateString;
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
- const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
- const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
- const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
- const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
- const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
+ const titleHeight = () => styleProvider?.(doc, props, StyleProp.TitleHeight);
+ const backgroundCol = () => styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
+ const color = () => styleProvider?.(doc, props, StyleProp.Color);
+ const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity);
+ const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle);
// prettier-ignore
switch (property.split(':')[0]) {
- case StyleProp.TreeViewIcon:
+ case StyleProp.TreeViewIcon: {
const img = ImageCast(doc?.icon, ImageCast(doc?.data));
if (img) {
const ext = extname(img.url.href);
@@ -111,16 +149,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
}
return Doc.toIcon(doc, isOpen);
- case StyleProp.TreeViewSortings:
+ }
+ case StyleProp.TreeViewSortings: {
const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: <BsArrowUp/> };
if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
allSorts[TreeSort.WhenAdded] = { 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) {
+ if (doc && !doc.layout_disableBrushing && !disableBrushing) {
const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
@@ -135,35 +175,36 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
return undefined;
- case StyleProp.DocContents:return undefined;
- case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
- 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.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
- case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
- case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
- case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
- case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
+ case StyleProp.DocContents: return undefined;
+ case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
+ case StyleProp.Opacity: return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+ case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color()));
+ case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
+ case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+ case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
+ case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
+ case StyleProp.ShowCaption: return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+ case StyleProp.TitleHeight: return Math.min(4,(DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
return (
(doc &&
- !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
- !props?.LayoutTemplateString &&
+ !(DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
+ !LayoutTemplateString &&
!doc.presentation_targetDoc &&
- !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
- props?.layout_showTitle?.() !== '' &&
+ !LayoutTemplateString?.includes(KeyValueBox.name) &&
+ layout_showTitle?.() !== '' &&
StrCast(
doc._layout_showTitle,
- props?.layout_showTitle?.() ||
+ layout_showTitle?.() ||
(!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
- ? doc.author === Doc.CurrentUserEmail
+ ? doc.author === ClientUtils.CurrentUserEmail()
? StrCast(Doc.UserDoc().layout_showTitle)
: remoteDocHeader
: '')
)) ||
''
);
- case StyleProp.Color:
+ case StyleProp.Color: {
if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userBackgroundColor;
if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
@@ -171,30 +212,33 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
if (docColor) return docColor;
const backColor = backgroundCol();
return backColor ? lightOrDark(backColor) : undefined;
- case StyleProp.BorderRounding:
+ }
+ case StyleProp.BorderRounding: {
const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
- case StyleProp.BorderPath:
+ }
+ case StyleProp.BorderPath: {
const borderPath = Doc.IsComicStyle(doc) &&
- props?.renderDepth &&
- !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
- return !borderPath
- ? null
- : {
- clipPath: `path('${borderPath.path}')`,
- jsx: (
- <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
- <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
- <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
- </svg>
- </div>
- ),
- };
+ renderDepth &&
+ !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 };
+ return !borderPath
+ ? null
+ : {
+ clipPath: `path('${borderPath.path}')`,
+ jsx: (
+ <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
+ <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
+ <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
+ </svg>
+ </div>
+ ),
+ };
+ }
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')) ||
+ (doc?.type === DocumentType.RTF && !layoutShowTitle()?.includes('noMargin')) ||
doc?.type === DocumentType.LABEL) &&
- layout_showTitle() &&
+ layoutShowTitle() &&
!StrCast(doc?.layout_showTitle).includes(':hover')
? titleHeight()
: 0;
@@ -231,13 +275,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
? undefined
: doc?._type_collection === CollectionViewType.Stacking ?
(Colors.DARK_GRAY)
- : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
+ : Cast((renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
break;
- //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
+ // if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (Colors.WHITE);
}
- if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
- return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+ if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && styleProvider) {
+ return styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
}
return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
}
@@ -251,20 +295,21 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
doc?.layout_boxShadow,
doc?._type_collection === CollectionViewType.Pile
? '4px 4px 10px 2px'
- : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString
+ : lockedPosition() || doc?.isGroup || LayoutTemplateString
? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
: `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
);
case DocumentType.LABEL:
if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
+ // eslint-disable-next-line no-fallthrough
default:
return doc.z
? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
- : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters
- ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ : containerViewPath?.().lastElement()?.Document._freeform_useClusters
+ ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: NumCast(doc.group, -1) !== -1
- ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: lockedPosition()
? undefined // if it's a background & has a cluster color, make the shadow spread really big
: fieldKey.includes('_inline') // if doc is an inline document in a text box
@@ -275,23 +320,23 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
case StyleProp.PointerEvents:
- 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 (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+ if (StrCast(doc?.pointerEvents) && !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 (LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
- if (props?.pointerEvents?.() === 'none') return 'none';
+ if (pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
- if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
+ if (isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
isGroup )? undefined: 'all'
- if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
+ if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
- case StyleProp.Decorations:
+ case StyleProp.Decorations: {
const lock = () => doc?.pointerEvents !== 'none' ? null : (
<div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
<FontAwesomeIcon icon='lock' size="lg" />
</div>
);
const paint = () => !doc?.onPaint ? null : (
- <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+ <div className={`styleProvider-paint${DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
<FontAwesomeIcon icon='pen' size="lg" />
</div>
);
@@ -300,8 +345,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const showFilterIcon =
StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
? 'green' // #18c718bd' //'hasFilter'
- : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length
- ? 'orange' //'inheritsFilter'
+ : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
+ ? 'orange' // 'inheritsFilter'
: undefined;
return !showFilterIcon ? null : (
<div className="styleProvider-filter">
@@ -309,8 +354,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
type={Type.TERT}
dropdownType={DropdownType.CLICK}
fillWidth
- iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
- closeOnSelect={true}
+ // eslint-disable-next-line react/no-unstable-nested-components
+ iconProvider={() => <div className='styleProvider-filterShift'><FaFilter/></div>}
+ closeOnSelect
setSelectedVal={
action((dv) => {
(dv as any).select(false);
@@ -331,7 +377,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
"this view inherits filters from one of its parents"}
color={SettingsManager.userColor}
background={showFilterIcon}
- items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])]
+ items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])]
.filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
.map(dv => ({
text: StrCast(dv?.Document.title),
@@ -343,9 +389,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
);
};
const audio = () => {
- const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
- const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
- if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
+ const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+ const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
+ if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
return (
<Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
@@ -363,6 +409,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
{audio()}
</>
);
+ }
+ default:
}
}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5df0bea1a..1d02568dd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,12 +1,13 @@
-import { computed, observable, ObservableSet, runInAction } from 'mobx';
+import { computed, ObservableSet, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, DocCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
+import { emptyFunction } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { Transform } from '../util/Transform';
@@ -16,21 +17,6 @@ import { DefaultStyleProvider } from './StyleProvider';
import './TemplateMenu.scss';
@observer
-class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
- render() {
- if (this.props.template) {
- return (
- <li className="templateToggle">
- <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event, this.props.template)} />
- {this.props.template}
- </li>
- );
- } else {
- return null;
- }
- }
-}
-@observer
class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
render() {
return (
@@ -50,12 +36,25 @@ export interface TemplateMenuProps {
export class TemplateMenu extends React.Component<TemplateMenuProps> {
_addedKeys = new ObservableSet();
_customRef = React.createRef<HTMLInputElement>();
- @observable private _hidden: boolean = true;
+
+ componentDidMount() {
+ !this._addedKeys && (this._addedKeys = new ObservableSet());
+ [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
+ .filter(key => key.startsWith('layout_') && (
+ StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
+ DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
+ ))
+ .forEach(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
+ }
+ @computed get scriptField() {
+ const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
+ return script ? () => script : undefined;
+ }
toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true));
};
- toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
+ toggleDefault = (): void => {
this.props.docViews.map(dv => dv.switchViews(false, 'layout'));
};
@@ -65,21 +64,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
runInAction(() => this._addedKeys.add(this._customRef.current!.value));
}
};
- componentDidMount() {
- !this._addedKeys && (this._addedKeys = new ObservableSet());
- [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
- .filter(key => key.startsWith('layout_') && (
- StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
- DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
- ))
- .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
- }
return100 = () => 300;
- @computed get scriptField() {
- const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
- return script ? () => script : undefined;
- }
templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
return StrCast(selDoc.layout_fieldKey) === 'layout_' + template ? 'check' : 'unchecked';
@@ -88,10 +74,11 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
TraceMobx();
const firstDoc = this.props.docViews[0].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> = [];
templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
+ // eslint-disable-next-line no-return-assign
addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
this._addedKeys &&
Array.from(this._addedKeys)
@@ -122,10 +109,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
addDocTab={returnFalse}
PanelWidth={this.return100}
PanelHeight={this.return100}
- treeViewHideHeaderFields={true}
- treeViewHideTitle={true}
- dontRegisterView={true}
- fieldKey={'data'}
+ treeViewHideHeaderFields
+ treeViewHideTitle
+ dontRegisterView
+ fieldKey="data"
moveDocument={returnFalse}
removeDocument={returnFalse}
addDocument={returnFalse}
@@ -135,10 +122,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
}
}
-ScriptingGlobals.add(function switchView(doc: Doc, template: Doc | undefined) {
- if (template?.dragFactory) {
- template = Cast(template.dragFactory, Doc, null);
- }
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function switchView(doc: Doc, templateIn: Doc | undefined) {
+ const template = templateIn?.dragFactory ? Cast(templateIn.dragFactory, Doc, null) : templateIn;
const templateTitle = StrCast(template?.title);
return templateTitle && DocUtils.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
});
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
deleted file mode 100644
index 436cb688f..000000000
--- a/src/client/views/Touchable.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import * as React from 'react';
-import { action } from 'mobx';
-import { InteractionUtils } from '../util/InteractionUtils';
-
-const HOLD_DURATION = 1000;
-
-export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> {
- //private holdTimer: NodeJS.Timeout | undefined;
- private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
- protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected _touchDrag: boolean = false;
- protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
-
- public FirstX: number = 0;
- public FirstY: number = 0;
- public SecondX: number = 0;
- public SecondY: number = 0;
-
- /**
- * When a touch even starts, we keep track of each touch that is associated with that event
- */
- @action
- protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- const actualPts: React.Touch[] = [];
- const te = me.touchEvent;
- // loop through all touches on screen
- for (const pt of me.touches) {
- actualPts.push(pt);
- if (this.prevPoints.has(pt.identifier)) {
- this.prevPoints.set(pt.identifier, pt);
- }
- // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives
- for (const tPt of me.changedTouches) {
- if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) {
- // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
- // and this seems to be the only way of differentiating pen and touch on touch events
- if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) {
- this.prevPoints.set(pt.identifier, pt);
- }
- }
- }
- }
-
- const ptsToDelete: number[] = [];
- this.prevPoints.forEach(pt => {
- if (!actualPts.includes(pt)) {
- ptsToDelete.push(pt.identifier);
- }
- });
-
- ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
-
- if (this.prevPoints.size) {
- switch (this.prevPoints.size) {
- case 1:
- this.handle1PointerDown(te, me);
- te.persist();
- // -- code for radial menu --
- // if (this.holdTimer) {
- // clearTimeout(this.holdTimer)
- // this.holdTimer = undefined;
- // }
- break;
- case 2:
- this.handle2PointersDown(te, me);
- break;
- }
- }
- };
-
- /**
- * Handle touch move event
- */
- @action
- protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- const te = me.touchEvent;
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-
- // if we're not actually moving a lot, don't consider it as dragging yet
- if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
- this._touchDrag = true;
- switch (myTouches.length) {
- case 1:
- this.handle1PointerMove(te, me);
- break;
- case 2:
- this.handle2PointersMove(te, me);
- break;
- }
-
- for (const pt of me.touches) {
- if (pt && this.prevPoints.has(pt.identifier)) {
- this.prevPoints.set(pt.identifier, pt);
- }
- }
- };
-
- @action
- protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- // remove all the touches associated with the event
- const te = me.touchEvent;
- for (const pt of me.changedTouches) {
- if (pt) {
- if (this.prevPoints.has(pt.identifier)) {
- this.prevPoints.delete(pt.identifier);
- }
- }
- }
- this._touchDrag = false;
- te.stopPropagation();
-
- // if (e.targetTouches.length === 0) {
- // this.prevPoints.clear();
- // }
-
- if (this.prevPoints.size === 0) {
- this.cleanUpInteractions();
- }
- e.stopPropagation();
- };
-
- cleanUpInteractions = (): void => {
- this.removeMoveListeners();
- this.removeEndListeners();
- };
-
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
- e.stopPropagation();
- e.preventDefault();
- };
-
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
- e.stopPropagation();
- e.preventDefault();
- };
-
- handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- };
-
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- };
-
- handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- e.stopPropagation();
- me.touchEvent.stopPropagation();
- this.removeMoveListeners();
- this.removeEndListeners();
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
- this.addHoldMoveListeners();
- this.addHoldEndListeners();
- };
-
- addMoveListeners = () => {
- const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchMove', handler);
- this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler);
- };
- addEndListeners = () => {
- const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchEnd', handler);
- this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler);
- };
-
- addHoldMoveListeners = () => {
- const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchHoldMove', handler);
- this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler);
- };
-
- addHoldEndListeners = () => {
- const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchHoldEnd', handler);
- this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler);
- };
-
- removeMoveListeners = () => this.moveDisposer?.();
- removeEndListeners = () => this.endDisposer?.();
- removeHoldMoveListeners = () => this.holdMoveDisposer?.();
- removeHoldEndListeners = () => this.holdEndDisposer?.();
-
- handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- // e.stopPropagation();
- // me.touchEvent.stopPropagation();
- };
-
- handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- e.stopPropagation();
- me.touchEvent.stopPropagation();
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
-
- me.touchEvent.stopPropagation();
- me.touchEvent.preventDefault();
- };
-
- handleHandDown = (e: React.TouchEvent) => {
- // e.stopPropagation();
- // e.preventDefault();
- };
-}
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 068143225..2d461c0ab 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -1,6 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tooltip } from '@mui/material';
import { Popup, Type } from 'browndash-components';
-import { observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { StrCast } from '../../fields/Types';
@@ -15,19 +16,19 @@ export class UndoStack extends React.Component<UndoStackProps> {
const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
return (
- <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}>
+ <Tooltip title="undo stack (if it stays yellow, undo is broken - you should reload Dash)">
<div>
<div className="undoStack-outerContainer">
<Popup
text="stack"
color={color}
background={background}
- placement={`top-start`}
+ placement="top-start"
type={Type.TERT}
popup={
<div
className="undoStack-commandsContainer"
- ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
+ ref={r => r?.scroll({ behavior: 'auto', top: (r?.scrollHeight ?? 0) + 20 })}
style={{
background,
color,
@@ -35,12 +36,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
{Array.from(UndoManager.undoStackNames).map((name, i) => (
<div
className="undoStack-resultContainer"
+ // eslint-disable-next-line react/no-array-index-key
key={i}
- onClick={e => {
+ onClick={() => {
const size = UndoManager.undoStackNames.length;
for (let n = 0; n < size - i; n++) UndoManager.Undo();
}}>
- <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
+ <div className="undoStack-commandString">{StrCast(name).replace(/[^.]*\./, '')}</div>
</div>
))}
{Array.from(UndoManager.redoStackNames)
@@ -48,12 +50,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
.map((name, i) => (
<div
className="undoStack-resultContainer"
+ // eslint-disable-next-line react/no-array-index-key
key={i}
- onClick={e => {
+ onClick={() => {
for (let n = 0; n <= i; n++) UndoManager.Redo();
}}>
<div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {StrCast(name).replace(/[^\.]*\./, '')}
+ {StrCast(name).replace(/[^.]*\./, '')}
</div>
</div>
))}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index cc4da1694..57498f39f 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -4,10 +4,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
+import { Utils, emptyFunction } from '../../../Utils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
+// eslint-disable-next-line import/extensions
import clamp from '../../util/clamp';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { FieldViewProps } from '../nodes/FieldView';
@@ -45,13 +47,13 @@ import { Track } from './Track';
@observer
export class Timeline extends ObservableReactComponent<FieldViewProps> {
- //readonly constants
+ // readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
private readonly MAX_CONTAINER_HEIGHT: number = 800;
private readonly DEFAULT_TICK_INCREMENT: number = 1000;
- //height variables
+ // height variables
private DEFAULT_CONTAINER_HEIGHT: number = 330;
private MIN_CONTAINER_HEIGHT: number = 205;
@@ -60,7 +62,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
makeObservable(this);
}
- //react refs
+ // react refs
@observable private _trackbox = React.createRef<HTMLDivElement>();
@observable private _titleContainer = React.createRef<HTMLDivElement>();
@observable private _timelineContainer = React.createRef<HTMLDivElement>();
@@ -68,7 +70,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@observable private _roundToggleRef = React.createRef<HTMLDivElement>();
@observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
- //boolean vars and instance vars
+ // boolean vars and instance vars
@observable private _currentBarX: number = 0;
@observable private _windSpeed: number = 1;
@observable private _totalLength: number = 0;
@@ -77,11 +79,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT;
@observable private _tickSpacing = this.DEFAULT_TICK_SPACING;
@observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
- @observable private _time = 100000; //DEFAULT
+ @observable private _time = 100000; // DEFAULT
@observable private _playButton = faPlayCircle;
@observable private _titleHeight = 0;
- @observable public IsPlaying: boolean = false; //scrubber playing
+ @observable public IsPlaying: boolean = false; // scrubber playing
/**
* collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
@@ -95,30 +97,30 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
return DocListCast(this._props.Document[this._props.fieldKey]);
}
- /////////lifecycle functions////////////
+ /// //////lifecycle functions////////////
@action
componentDidMount() {
- const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight
- this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
- this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
- this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
+ const relativeHeight = window.innerHeight / 20; // sets height to arbitrary size, relative to innerHeight
+ this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
+ this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
+ this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
if (!this._props.Document.AnimationLength) {
- //if animation length did not exist
- this._props.Document.AnimationLength = this._time; //set it to default time
+ // if animation length did not exist
+ this._props.Document.AnimationLength = this._time; // set it to default time
} else {
- this._time = NumCast(this._props.Document.AnimationLength); //else, set time to animationlength stored from before
+ this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
}
- this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself)
- this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
- this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
- this._props.Document.isATOn = !this._props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on
+ this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
+ this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+ this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
+ this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
this.toggleHandle();
}
componentWillUnmount() {
- this._props.Document.AnimationLength = this._time; //save animation length
+ this._props.Document.AnimationLength = this._time; // save animation length
}
- /////////////////////////////////////////////////
+ /// //////////////////////////////////////////////
/**
* React Functional Component
@@ -146,7 +148,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel);
};
- //for playing
+ // for playing
onPlay = (e: React.MouseEvent) => {
e.stopPropagation();
this.play();
@@ -179,7 +181,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
e.preventDefault();
e.stopPropagation();
if (this._windSpeed < 64) {
- //max speed is 32
+ // max speed is 32
this._windSpeed = this._windSpeed * 2;
}
};
@@ -213,7 +215,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
const scrubberbox = this._infoContainer.current!;
const left = scrubberbox.getBoundingClientRect().left;
const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale;
- this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+ this.changeCurrentBarX(offsetX + this._visibleStart); // changes scrubber to clicked scrubber position
return false;
};
@@ -353,12 +355,12 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
* tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode)
*/
private timelineToolBox = (scale: number, totalTime: number) => {
- const size = 40 * scale; //50 is default
+ const size = 40 * scale; // 50 is default
const iconSize = 25;
const width: number = this._props.PanelWidth();
const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
- //decides if information should be omitted because the timeline is very small
+ // decides if information should be omitted because the timeline is very small
// if its less than 950 pixels then it's going to be overlapping
let modeString = modeType,
overviewString = '',
@@ -467,7 +469,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
this._props.Document.isATOn = !this._props.Document.isATOn;
if (!BoolCast(this._props.Document.isATOn)) {
- //turning on playmode...
+ // turning on playmode...
roundToggle.style.transform = 'translate(0px, 0px)';
roundToggle.style.animationName = 'turnoff';
roundToggleContainer.style.animationName = 'turnoff';
@@ -475,7 +477,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
timelineContainer.style.top = `${-this._containerHeight}px`;
this.toPlay();
} else {
- //turning on authoring mode...
+ // turning on authoring mode...
roundToggle.style.transform = 'translate(20px, 0px)';
roundToggle.style.animationName = 'turnon';
roundToggleContainer.style.animationName = 'turnon';
@@ -488,8 +490,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@action.bound
changeLengths() {
if (this._infoContainer.current) {
- this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
- this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
+ this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+ this._visibleStart = this._infoContainer.current.scrollLeft; // where the div starts
}
}
@@ -513,7 +515,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
}
}
} else {
- //TODO: remove undefineds and duplicates
+ // TODO: remove undefineds and duplicates
}
});
return longestTime;
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 97a571dc4..20da21cb9 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -9,6 +10,7 @@ import './TimelineMenu.scss';
@observer
export class TimelineMenu extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: TimelineMenu;
@observable private _opacity = 0;
@@ -67,16 +69,19 @@ export class TimelineMenu extends React.Component {
this._currentMenu.push(
<div key={Utils.GenerateGuid()} className="timeline-menu-item">
<FontAwesomeIcon icon={faChartLine as IconLookup} size="lg" />
- <p
- className="timeline-menu-desc"
- onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- event(e);
- this.closeMenu();
- }}>
- {title}
- </p>
+ {
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+ <p
+ className="timeline-menu-desc"
+ onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ event(e);
+ this.closeMenu();
+ }}>
+ {title}
+ </p>
+ }
</div>
);
}
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 489c4dcde..7bf685c9e 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unused-prop-types */
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -32,7 +33,6 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
@observable private visibleTime: number = 0;
@observable private currentX: number = 0;
@observable private visibleStart: number = 0;
- private readonly DEFAULT_HEIGHT = 50;
private readonly DEFAULT_WIDTH = 300;
componentDidMount() {
@@ -50,9 +50,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
);
}
- componentWillUnmount = () => {
+ componentWillUnmount() {
this._authoringReaction && this._authoringReaction();
- };
+ }
@action
setOverviewWidth() {
@@ -109,7 +109,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
e.preventDefault();
e.stopPropagation();
const scrubberRef = this._scrubberRef.current!;
- const left = scrubberRef.getBoundingClientRect().left;
+ const { left } = scrubberRef.getBoundingClientRect();
const offsetX = Math.round(e.clientX - left);
this.props.changeCurrentBarX((offsetX / this.activeOverviewWidth) * this.props.totalLength + this.props.currentBarX);
};
@@ -152,17 +152,17 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
const timeline = this.props.isAuthoring
? [
<div key="timeline-overview-container" className="timeline-overview-container overviewBar" id="timelineOverview" ref={this.authoringContainer}>
- <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown}></div>,
+ <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown} />,
<div ref={this._scrubberRef} key="2" className="timeline-overview-scrubber-container" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}>
- <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head"></div>
+ <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head" />
</div>
</div>,
]
: [
<div key="1" className="timeline-play-bar overviewBar" id="timelinePlay" ref={this.playbackContainer}>
- <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}></div>
+ <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown} />
</div>,
- <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }}></div>,
+ <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }} />,
];
return (
<div className="timeline-flex">
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 490a14be5..1e4ed74be 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -73,7 +73,7 @@ export class Track extends ObservableReactComponent<IProps> {
this._timelineVisibleReaction?.();
this._autoKfReaction?.();
}
- ////////////////////////////////
+ // //////////////////////////////
getLastRegionTime = () => {
let lastTime: number = 0;
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index cbcc980a9..43e5a68eb 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,7 +1,8 @@
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
import { CollectionStackingView } from './CollectionStackingView';
@@ -24,16 +25,13 @@ export class CollectionCalendarView extends CollectionSubView() {
removeCalendar = () => {};
- addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+ addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean =>
// bring up calendar modal with option to create a calendar
- return true;
- };
+ true;
_stackRef = React.createRef<CollectionStackingView>();
- panelHeight = () => {
- return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
- };
+ panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
// most recent calendar should come first
sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
@@ -45,18 +43,18 @@ export class CollectionCalendarView extends CollectionSubView() {
if (aFromDate > bFromDate) {
return -1; // a comes first
- } else if (aFromDate < bFromDate) {
+ }
+ if (aFromDate < bFromDate) {
+ return 1; // b comes first
+ }
+ // start dates are the same
+ if (aToDate > bToDate) {
+ return -1; // a comes first
+ }
+ if (aToDate < bToDate) {
return 1; // b comes first
- } else {
- // start dates are the same
- if (aToDate > bToDate) {
- return -1; // a comes first
- } else if (aToDate < bToDate) {
- return 1; // b comes first
- } else {
- return 0; // same start and end dates
- }
}
+ return 0; // same start and end dates
};
screenToLocalTransform = () =>
@@ -73,6 +71,7 @@ export class CollectionCalendarView extends CollectionSubView() {
return (
<div className="collectionCalendarView">
<CollectionStackingView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
ref={this._stackRef}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 4e4bd43bf..8d8f41126 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnZero } from '../../../ClientUtils';
+import { Utils } from '../../../Utils';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -14,12 +17,13 @@ import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FieldView';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
+
const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
@computed get scrollSpeed() {
- return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
+ return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed
}
constructor(props: any) {
super(props);
@@ -48,16 +52,16 @@ export class CollectionCarousel3DView extends CollectionSubView() {
panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
- isChildContentActive = () => (this.isContentActive() ? true : false);
+ isChildContentActive = () => !!this.isContentActive();
childScreenToLocal = () =>
this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
.ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
.translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
.scale(1 / this.centerScale);
- focus = (anchor: Doc, options: FocusViewOptions) => {
+ focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
- if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+ if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined;
options.didMove = true;
const target = DocCast(anchor.annotationOn) ?? anchor;
const index = docs.indexOf(target);
@@ -66,37 +70,34 @@ export class CollectionCarousel3DView extends CollectionSubView() {
};
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._carousel_index);
- const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
- return (
- <DocumentView
- {...this._props}
- Document={childPair.layout}
- TemplateDataDocument={childPair.data}
- //suppressSetHeight={true}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- layout_fitWidth={undefined}
- onDoubleClickScript={this.onChildDoubleClick}
- renderDepth={this._props.renderDepth + 1}
- LayoutTemplate={this._props.childLayoutTemplate}
- LayoutTemplateString={this._props.childLayoutString}
- focus={this.focus}
- ScreenToLocalTransform={this.childScreenToLocal}
- isContentActive={this.isChildContentActive}
- isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- />
- );
- };
-
- return this.carouselItems.map((childPair, index) => {
- return (
- <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
- {displayDoc(childPair)}
- </div>
- );
- });
+ const displayDoc = (childPair: { layout: Doc; data: Doc }) => (
+ <DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...this._props}
+ Document={childPair.layout}
+ TemplateDataDocument={childPair.data}
+ // suppressSetHeight={true}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ layout_fitWidth={undefined}
+ onDoubleClickScript={this.onChildDoubleClick}
+ renderDepth={this._props.renderDepth + 1}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ focus={this.focus}
+ ScreenToLocalTransform={this.childScreenToLocal}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ />
+ );
+
+ return this.carouselItems.map((childPair, index) => (
+ <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
+ {displayDoc(childPair)}
+ </div>
+ ));
}
changeSlide = (direction: number) => {
@@ -124,21 +125,21 @@ export class CollectionCarousel3DView extends CollectionSubView() {
};
toggleAutoScroll = (direction: number) => {
- this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
+ this.layoutDoc.autoScrollOn = !this.layoutDoc.autoScrollOn;
this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
};
fadeScrollButton = () => {
window.setTimeout(() => {
- !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
+ !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); // fade away after 1.5s if it's not clicked.
}, 1500);
};
@computed get buttons() {
return (
<div className="arrow-buttons">
- <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
- <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
+ <div title="click to go back" key="back" className="carousel3DView-back" onClick={() => this.onArrowClick(-1)} />
+ <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={() => this.onArrowClick(1)} />
{/* {this.autoScrollButton} */}
</div>
);
@@ -149,17 +150,25 @@ export class CollectionCarousel3DView extends CollectionSubView() {
return (
<>
<div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-left" size="1x" />}
</div>
<div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-right" size="1x" />}
</div>
</>
);
}
@computed get dots() {
- return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+ return this.carouselItems.map((_child, index) => (
+ <div
+ key={Utils.GenerateGuid()}
+ className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`}
+ onClick={() => {
+ this.layoutDoc._carousel_index = index;
+ }}
+ />
+ ));
}
@computed get translateX() {
const index = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9c370bfbb..51352d3e2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,10 +1,15 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { StyleProp } from '../StyleProvider';
import { DocumentView } from '../nodes/DocumentView';
@@ -12,7 +17,6 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView } from './CollectionSubView';
-import { DocumentType } from '../../documents/DocumentTypes';
@observer
export class CollectionCarouselView extends CollectionSubView() {
@@ -106,10 +110,10 @@ export class CollectionCarouselView extends CollectionSubView() {
return (
<>
<div key="back" className="carouselView-back" onClick={this.goback}>
- <FontAwesomeIcon icon={'chevron-left'} size={'2x'} />
+ <FontAwesomeIcon icon="chevron-left" size="2x" />
</div>
<div key="fwd" className="carouselView-fwd" onClick={this.advance}>
- <FontAwesomeIcon icon={'chevron-right'} size={'2x'} />
+ <FontAwesomeIcon icon="chevron-right" size="2x" />
</div>
</>
);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b2897a9b7..fefaf6591 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -2,25 +2,26 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import * as GoldenLayout from '../../../client/goldenLayout';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy } from '../../../ClientUtils';
import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import * as GoldenLayout from '../../goldenLayout';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
@@ -32,11 +33,12 @@ import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import { TabDocView } from './TabDocView';
-import { ComputedField } from '../../../fields/ScriptField';
+
const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: CollectionDockingView | undefined = undefined;
public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
return {
@@ -68,7 +70,7 @@ export class CollectionDockingView extends CollectionSubView() {
super(props);
makeObservable(this);
if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
- //Why is this here?
+ // Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
DragManager.StartWindowDrag = this.StartOtherDrag;
@@ -118,7 +120,7 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
if (CollectionDockingView.Instance) {
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tabView => (panelName ? tabView.contentItem.config.props.panelName === panelName : tabView.DashDoc === document));
if (tab) {
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
if (j !== -1) {
@@ -145,7 +147,7 @@ export class CollectionDockingView extends CollectionSubView() {
stack.contentItems[activeContentItemIndex].remove();
return instance.layoutChanged();
}
- const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+ const tab = Array.from(instance.tabMap.keys()).find(tabView => tabView.contentItem.config.props.panelName === panelName);
if (tab) {
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) {
@@ -170,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() {
public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
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 && !tab.contentItem.config.props.keyValue && !keyValue);
+ const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tabView => tabView.DashDoc === document && !tabView.contentItem.config.props.keyValue && !keyValue);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
return true;
@@ -201,6 +203,7 @@ export class CollectionDockingView extends CollectionSubView() {
} else if (instance._goldenLayout.root.contentItems[0].isRow) {
// if row
switch (pullSide) {
+ // eslint-disable-next-line default-case-last
default:
case OpenWhereMod.none:
case OpenWhereMod.right:
@@ -210,7 +213,7 @@ export class CollectionDockingView extends CollectionSubView() {
glayRoot.contentItems[0].addChild(newContentItem(), 0);
break;
case OpenWhereMod.top:
- case OpenWhereMod.bottom:
+ case OpenWhereMod.bottom: {
// if not going in a row layout, must add already existing content into column
const rowlayout = glayRoot.contentItems[0];
const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout);
@@ -229,6 +232,7 @@ export class CollectionDockingView extends CollectionSubView() {
rowlayout.config.height = 50;
newItem.config.height = 50;
+ }
}
} else {
// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
@@ -241,7 +245,7 @@ export class CollectionDockingView extends CollectionSubView() {
break;
case 'left':
case 'right':
- default:
+ default: {
// if not going in a row layout, must add already existing content into column
const collayout = glayRoot.contentItems[0];
const newRow = collayout.layoutManager.createContentItem({ type: 'row' }, instance._goldenLayout);
@@ -260,6 +264,7 @@ export class CollectionDockingView extends CollectionSubView() {
collayout.config.width = 50;
newItem.config.width = 50;
+ }
}
}
instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
@@ -278,10 +283,10 @@ export class CollectionDockingView extends CollectionSubView() {
}
setupGoldenLayout = async () => {
if (this._unmounting) return;
- //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
+ // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
const config = StrCast(this.Document.dockingConfig);
if (config) {
- const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = config.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
await Promise.all(docids.map(id => DocServer.GetRefField(id)));
@@ -289,12 +294,13 @@ export class CollectionDockingView extends CollectionSubView() {
if (this._goldenLayout) {
if (config === JSON.stringify(this._goldenLayout.toConfig())) {
return;
- } else {
- try {
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- } catch (e) {}
+ }
+ try {
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) {
+ /* empty */
}
this.tabMap.clear();
this._goldenLayout.destroy();
@@ -323,7 +329,7 @@ export class CollectionDockingView extends CollectionSubView() {
*/
titleChanged = (target: any, value: any) => {
const title = Field.toString(value);
- if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+ if (title.startsWith('@') && !title.substring(1).match(/[()[\]@]/) && title.length > 1) {
const embedding = DocListCast(target.proto_embeddings).lastElement();
embedding && Doc.AddToMyPublished(embedding);
} else if (!title.startsWith('@')) {
@@ -337,7 +343,7 @@ export class CollectionDockingView extends CollectionSubView() {
if (this._containerRef.current) {
this._lightboxReactionDisposer = reaction(
() => LightboxView.LightboxDoc,
- doc => setTimeout(() => !doc && this.onResize(undefined))
+ doc => setTimeout(() => !doc && this.onResize())
);
new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
this._reactionDisposer = reaction(
@@ -356,12 +362,12 @@ export class CollectionDockingView extends CollectionSubView() {
{ fireImmediately: true }
);
reaction(
- () => [SettingsManager.userBackgroundColor, SettingsManager.userBackgroundColor],
+ () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor],
() => {
clearStyleSheetRules(CollectionDockingView._highlightStyleSheet);
- addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SettingsManager.userBackgroundColor} !important` });
- addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SettingsManager.userColor} !important` });
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${SettingsManager.userBackgroundColor} !important` });
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SnappingManager.userBackgroundColor} !important` });
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SnappingManager.userColor} !important` });
+ addStyleSheetRule(SnappingManager.SettingsStyle, 'lm_header', { background: `${SnappingManager.userBackgroundColor} !important` });
},
{ fireImmediately: true }
);
@@ -374,7 +380,9 @@ export class CollectionDockingView extends CollectionSubView() {
try {
this._goldenLayout.unbind('stackCreated', this.stackCreated);
this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
this._goldenLayout?.destroy();
window.removeEventListener('resize', this.onResize);
window.removeEventListener('mouseup', this.onPointerUp);
@@ -384,7 +392,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
@action
- onResize = (event: any) => {
+ onResize = () => {
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
!LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
@@ -392,7 +400,7 @@ export class CollectionDockingView extends CollectionSubView() {
endUndoBatch = () => {
const json = JSON.stringify(this._goldenLayout.toConfig());
- const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
const docs = !docids
? []
@@ -415,7 +423,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
@action
- onPointerUp = (e: MouseEvent): void => {
+ onPointerUp = (): void => {
window.removeEventListener('pointerup', this.onPointerUp);
DragManager.CompleteWindowDrag = undefined;
setTimeout(this.endUndoBatch, 100);
@@ -453,24 +461,27 @@ export class CollectionDockingView extends CollectionSubView() {
if (content) {
const _width = DivWidth(content);
const _height = DivHeight(content);
- return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
+ return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => {
const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
- proto['thumb_nativeWidth'] = _width;
- proto['thumb_nativeHeight'] = _height;
+ proto.thumb_nativeWidth = _width;
+ proto.thumb_nativeHeight = _height;
proto.thumb = new ImageField(iconFile);
});
}
+ return undefined;
}
public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
if (!doc) return undefined;
let json = StrCast(doc.dockingConfig);
if (clone) {
const cloned = await Doc.MakeClone(doc);
- Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
+ Array.from(cloned.map.entries()).forEach(entry => {
+ json = json.replace(entry[0], entry[1][Id]);
+ });
cloned.clone[DocData].dockingConfig = json;
return DashboardView.openDashboard(cloned.clone);
}
- const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || [];
const origtabs = origtabids
.map(id => DocServer.GetCachedRefField(id))
@@ -508,19 +519,20 @@ export class CollectionDockingView extends CollectionSubView() {
tabDestroyed = (tab: any) => {
this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
- if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) {
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
+ const dashDoc = tab.DashDoc;
+ if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) {
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, 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.Document) tab.DashDoc.embedContainer = undefined;
- if (!tab.DashDoc.embedContainer) {
- Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
- Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined;
+ if (!dashDoc.embedContainer) {
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
+ Doc.RemoveEmbedding(dashDoc, dashDoc);
}
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.Document;
- const fieldKey = CollectionDockingView.Instance.props.fieldKey;
- Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+ const { fieldKey } = CollectionDockingView.Instance.props;
+ Doc.RemoveDocFromList(dview, fieldKey, dashDoc);
this.tabMap.delete(tab);
tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
this.stateChanged();
@@ -531,8 +543,8 @@ export class CollectionDockingView extends CollectionSubView() {
tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
};
- stackCreated = (stack: any) => {
- stack = stack.header ? stack : stack.origin;
+ stackCreated = (stackIn: any) => {
+ const stack = stackIn.header ? stackIn : stackIn.origin;
stack.header?.element.on('mousedown', (e: any) => {
const dashboard = Doc.ActiveDashboard;
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
@@ -550,32 +562,29 @@ export class CollectionDockingView extends CollectionSubView() {
}
});
- let addNewDoc = undoable(
- action(() => {
- const dashboard = Doc.ActiveDashboard;
- if (dashboard) {
- dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
- const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this._props.PanelWidth(),
- _height: this._props.PanelHeight(),
- _layout_fitWidth: true,
- _freeform_backgroundGrid: true,
- title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
- });
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
- inheritParentAcls(this.dataDoc, docToAdd, false);
- CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
- }
- }),
- 'add new tab'
- );
+ const addNewDoc = undoable(() => {
+ const dashboard = Doc.ActiveDashboard;
+ if (dashboard) {
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
+ const docToAdd = Docs.Create.FreeformDocument([], {
+ _width: this._props.PanelWidth(),
+ _height: this._props.PanelHeight(),
+ _layout_fitWidth: true,
+ _freeform_backgroundGrid: true,
+ title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
+ });
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+ inheritParentAcls(this.dataDoc, docToAdd, false);
+ CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
+ }
+ }, 'add new tab');
stack.header?.controlsContainer
- .find('.lm_close') //get the close icon
- .off('click') //unbind the current click handler
+ .find('.lm_close') // get the close icon
+ .off('click') // unbind the current click handler
.click(
action(() => {
- //if (confirm('really close this?')) {
+ // if (confirm('really close this?')) {
if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) {
const batch = UndoManager.StartBatch('close stack');
stack.remove();
@@ -595,11 +604,11 @@ export class CollectionDockingView extends CollectionSubView() {
}
});
stack.header?.controlsContainer
- .find('.lm_maximise') //get the close icon
+ .find('.lm_maximise') // get the close icon
.click(() => setTimeout(this.stateChanged));
stack.header?.controlsContainer
- .find('.lm_popout') //get the popout icon
- .off('click') //unbind the current click handler
+ .find('.lm_popout') // get the popout icon
+ .off('click') // unbind the current click handler
.click(addNewDoc);
};
@@ -609,13 +618,14 @@ export class CollectionDockingView extends CollectionSubView() {
<div>
{href ? (
<img
+ alt="thumbnail of nested dashboard"
style={{ background: 'white', top: 0, position: 'absolute' }}
src={href} // + '?d=' + (new Date()).getTime()}
width={this._props.PanelWidth()}
height={this._props.PanelHeight()}
/>
) : (
- <p>nested dashboards has no thumbnail</p>
+ <p>nested dashboard has no thumbnail</p>
)}
</div>
) : (
@@ -625,6 +635,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openInLightbox(doc: any) {
LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
},
@@ -632,26 +643,37 @@ ScriptingGlobals.add(
'(doc: any)'
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openDoc(doc: any, where: OpenWhere) {
switch (where) {
case OpenWhere.addRight:
return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
case OpenWhere.overlay:
+ default:
// prettier-ignore
switch (doc) {
case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' });
+ default:
}
Doc.AddToMyOverlay(doc);
+ return true;
}
},
'opens up document in location specified',
'(doc: any)'
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openRepl() {
return 'openRepl';
},
'opens up document in screen overlay layer',
'(doc: any)'
);
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(async function snapshotDashboard() {
+ const batch = UndoManager.StartBatch('snapshot');
+ await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
+ batch.end();
+}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 7dcfd32bd..8803f6f79 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,9 +1,13 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction, numberRange } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
@@ -51,13 +55,19 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
@observable private collapsed: boolean = false;
@observable private _paletteOn = false;
private set _heading(value: string) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.heading = this.heading = value);
+ });
}
private set _color(value: string) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.color = this.color = value);
+ });
}
private set _collapsed(value: boolean) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value);
+ });
}
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -87,7 +97,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
if (this.collapsed) {
this._props.setDocHeight(this.heading, 20);
} else {
- const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
+ const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; // +15 accounts for the group header
const transformScale = this._props.screenToLocalTransform().Scale;
const trueHeight = rawHeight * transformScale;
this._props.setDocHeight(this.heading, trueHeight);
@@ -101,7 +111,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const key = this._props.pivotField;
const castedValue = this.getValue(this.heading);
if (this._props.parent.onInternalDrop(e, de)) {
- key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key)));
+ key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
}
return true;
}
@@ -117,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
this._createEmbeddingSelected = false;
const key = this._props.pivotField;
const castedValue = this.getValue(value);
@@ -140,7 +150,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
this._color = color;
};
- pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4'));
+ pointerEnteredRow = action(() => {
+ SnappingManager.IsDragging && (this._background = '#b4b4b4');
+ });
@action
pointerLeaveRow = () => {
@@ -152,13 +164,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
this._createEmbeddingSelected = false;
- const key = this._props.pivotField;
+ const { pivotField } = this._props;
const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = value;
- key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading));
+ pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
const docs = this._props.parent.childDocList;
- return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
+ return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
};
deleteRow = undoBatch(
@@ -197,21 +209,11 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (e.button === 0 && !e.ctrlKey) {
- setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e));
+ setupMoveUpEvents(this, e, this.headerMove, emptyFunction, clickEv => !this._props.chromeHidden && this.collapseSection(clickEv));
this._createEmbeddingSelected = false;
}
};
- /**
- * Returns true if a key is on the layout doc of the documents in the collection.
- */
- onLayoutDoc = (key: string): boolean => {
- DocListCast(this._props.parent.Document.data).forEach(doc => {
- if (Doc.Get(doc, key, true)) return true;
- });
- return false;
- };
-
renderColorPicker = () => {
const selected = this.color;
@@ -228,27 +230,29 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
return (
<div className="collectionStackingView-colorPicker">
<div className="colorOptions">
- <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
- <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
- <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
- <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
- <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
- <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
- <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div>
- <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div>
- <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div>
+ <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)} />
+ <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)} />
+ <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)} />
+ <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)} />
+ <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)} />
+ <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)} />
+ <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)} />
+ <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)} />
+ <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)} />
</div>
</div>
);
};
- toggleEmbedding = action(() => (this._createEmbeddingSelected = true));
- toggleVisibility = () => (this._collapsed = !this.collapsed);
+ toggleEmbedding = action(() => {
+ this._createEmbeddingSelected = true;
+ });
+ toggleVisibility = () => {
+ this._collapsed = !this.collapsed;
+ };
@action
- textCallback = (char: string) => {
- return this.addDocument('', false);
- };
+ textCallback = (/* char: string */) => this.addDocument('', false);
@computed get contentLayout() {
const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
@@ -262,22 +266,22 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
className="collectionStackingView-addDocumentButton"
style={
{
- //width: style.columnWidth / style.numGroupColumns,
- //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
+ // 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'} />
+ <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" />
</div>
) : null}
<div
- className={`collectionStackingView-masonryGrid`}
+ className="collectionStackingView-masonryGrid"
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`, ''),
+ gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''),
}}>
{this._props.parent.children(this._props.docList)}
</div>
@@ -289,7 +293,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const noChrome = this._props.chromeHidden;
const key = this._props.pivotField;
const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
- const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />;
+ const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
return this._props.Document.miniHeaders ? (
<div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
) : !this._props.headingObject ? null : (
@@ -303,6 +307,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionColor">
<button
+ type="button"
className="collectionStackingView-sectionColorButton"
onPointerDown={e =>
setupMoveUpEvents(
@@ -310,7 +315,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
e,
returnFalse,
emptyFunction,
- action(e => (this._paletteOn = !this._paletteOn))
+ action(() => {
+ this._paletteOn = !this._paletteOn;
+ })
)
}>
<FontAwesomeIcon icon="palette" size="lg" />
@@ -319,13 +326,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
</div>
)}
{noChrome ? null : (
- <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
+ <button type="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" onPointerDown={e => e.stopPropagation()}>
- <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
+ <button type="button" className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
</div>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 4f25f69ef..6dba9e155 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,10 +1,17 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/sort-comp */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
@@ -12,7 +19,8 @@ import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
@@ -34,6 +42,7 @@ interface CollectionMenuProps {
@observer
export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
+ // eslint-disable-next-line no-use-before-define
@observable static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined = undefined;
@@ -61,7 +70,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
@action
- toggleMenuPin = (e: React.MouseEvent) => {
+ toggleMenuPin = () => {
Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned;
if (!this.Pinned && this._left < 0) {
this.jumpTo(300, 300);
@@ -79,7 +88,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
};
@@ -123,7 +132,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
const hardCodedButtons = (
- <div className={`hardCodedButtons`}>
+ <div className="hardCodedButtons">
<Toggle
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
@@ -145,7 +154,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
</div>
);
- //dash col linear view buttons
+ // dash col linear view buttons
const contMenuButtons = (
<div
className="collectionMenu-container"
@@ -163,7 +172,9 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
interface CollectionViewMenuProps {
+ // eslint-disable-next-line react/no-unused-prop-types
type: CollectionViewType;
+ // eslint-disable-next-line react/no-unused-prop-types
fieldKey: string;
docView: DocumentView;
}
@@ -172,7 +183,7 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> {
- //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+ // (!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get document() {
return this.props.docView?.Document;
@@ -206,14 +217,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
params: ['target', 'source'],
title: 'child click view',
script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])',
- immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
+ immediate: undoBatch((source: Doc[]) => {
+ source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]));
+ }),
initialize: emptyFunction,
};
_contentCommand = {
params: ['target', 'source'],
title: 'set content',
script: 'getProto(this.target).data = copyField(this.source);',
- immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List<Doc>(source))),
+ immediate: undoBatch((source: Doc[]) => {
+ this.target[DocData].data = new List<Doc>(source);
+ }),
initialize: emptyFunction,
};
_onClickCommand = {
@@ -224,14 +239,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
getProto(this.proxy[0]).target = this.target.target;
getProto(this.proxy[0]).source = copyField(this.target.source);
}}`,
- immediate: undoBatch((source: Doc[]) => {}),
+ immediate: undoBatch(() => {}),
initialize: emptyFunction,
};
_viewCommand = {
params: ['target'],
title: 'bookmark view',
script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);",
- immediate: undoBatch((source: Doc[]) => {
+ immediate: undoBatch(() => {
this.target._freeform_panX = 0;
this.target._freeform_panY = 0;
this.target._freeform_scale = 1;
@@ -248,14 +263,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
params: ['target'],
title: 'fit content',
script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;',
- immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
+ immediate: undoBatch(() => {
+ this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;
+ }),
initialize: emptyFunction,
};
_fitContentCommand = {
params: ['target'],
title: 'toggle clusters',
script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;',
- immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
+ immediate: undoBatch(() => {
+ this.target._freeform_useClusters = !this.target._freeform_useClusters;
+ }),
initialize: emptyFunction,
};
_saveFilterCommand = {
@@ -263,7 +282,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
title: 'save filter',
script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']);
this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`,
- immediate: undoBatch((source: Doc[]) => {
+ immediate: undoBatch(() => {
this.target._childFilters = undefined;
this.target._searchFilterDocs = undefined;
}),
@@ -299,8 +318,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
private get _buttonizableCommands() {
switch (this.props.type) {
- default:
- return this._doc_commands;
case CollectionViewType.Freeform:
return this._freeform_commands;
case CollectionViewType.Tree:
@@ -319,6 +336,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
return this._freeform_commands;
case CollectionViewType.Carousel3D:
return this._freeform_commands;
+ default:
+ return this._doc_commands;
}
}
private _commandRef = React.createRef<HTMLInputElement>();
@@ -332,13 +351,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
- //@ts-ignore
- target._type_collection = e.target.selectedOptions[0].value;
+ target._type_collection = (e.target as any).selectedOptions[0].value;
};
commandChanged = (e: React.ChangeEvent) => {
- //@ts-ignore
- runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
+ runInAction(() => {
+ this._currentKey = (e.target as any).selectedOptions[0].value;
+ });
};
@action closeViewSpecs = () => {
@@ -356,7 +375,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData?.draggedDocuments.length) {
this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
@@ -369,16 +388,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ moveEv => {
const vtype = this.props.type;
const c = {
params: ['target'],
title: vtype,
script: `this.target._type_collection = '${StrCast(this.props.type)}'`,
- immediate: (source: Doc[]) => (this.document._type_collection = Doc.getDocTemplate(source?.[0])),
+ immediate: (source: Doc[]) => {
+ this.document._type_collection = Doc.getDocTemplate(source?.[0]);
+ },
initialize: emptyFunction,
};
- DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
+ DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
@@ -389,8 +410,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
- this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
+ moveEv => {
+ this._buttonizableCommands
+ ?.filter(c => c.title === this._currentKey)
+ .map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY));
return true;
},
emptyFunction,
@@ -405,11 +428,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget}>
<Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
<div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <button className={'antimodeMenu-button'}>
+ <button type="button" className="antimodeMenu-button">
<FontAwesomeIcon icon="bullseye" size="lg" />
</button>
<select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} />
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key="empty" value="" />
{this._buttonizableCommands?.map(cmd => (
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>
{cmd.title}
@@ -456,23 +479,19 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
if (docs instanceof Doc) {
const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(
- key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
- );
- return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
};
@action
@@ -482,9 +501,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
getSuggestionValue = (suggestion: string) => suggestion;
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- };
+ renderSuggestion = (suggestion: string) => <p>{suggestion}</p>;
onSuggestionFetch = async ({ value }: { value: string }) => {
const sugg = await this.getKeySuggestions(value);
@@ -572,12 +589,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
}
componentDidMount() {
- runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
+ runInAction(() => {
+ this.resize = this.props.docView.props.PanelWidth() < 700;
+ });
// listener to reduce text on chrome resize (panel resize)
this.resizeListenerDisposer = reaction(
() => this.panelWidth,
- newValue => (this.resize = newValue < 700)
+ newValue => {
+ this.resize = newValue < 700;
+ }
);
}
@@ -593,7 +614,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
* Sets the value of `numCols` on the grid's Document to the value entered.
*/
onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))();
+ if (e.currentTarget.valueAsNumber > 0)
+ undoBatch(() => {
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
+ })();
};
/**
@@ -622,7 +646,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
onIncrementButtonClick = () => {
this.clicked = true;
this.entered && (this.document.gridNumCols as number)--;
- undoBatch(() => (this.document.gridNumCols = this.numCols + 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols + 1;
+ })();
this.entered = false;
};
@@ -633,7 +659,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
this.clicked = true;
if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
- undoBatch(() => (this.document.gridNumCols = this.numCols - 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols - 1;
+ })();
if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
@@ -726,7 +754,13 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
<label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label>
</span>
- <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button>
+ <button
+ type="button"
+ onClick={() => {
+ this.document.gridResetLayout = true;
+ }}>
+ {!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}
+ </button>
</div>
);
}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index d8a0aebb1..2ba6f5bf4 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,7 +1,8 @@
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, Field, Opt } from '../../../fields/Doc';
+import { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils';
+import { Doc, Field, FieldType, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Copy, Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -9,14 +10,16 @@ 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 { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoable, undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { FieldsDropdown } from '../FieldsDropdown';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { DocumentView } from '../nodes/DocumentView';
@@ -27,7 +30,7 @@ import './CollectionNoteTakingView.scss';
import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
import { CollectionSubView } from './CollectionSubView';
-import { FieldsDropdown } from '../FieldsDropdown';
+
const _global = (window /* browser */ || global) /* node */ as any;
/**
@@ -56,7 +59,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
@computed get chromeHidden() {
- return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false;
+ return !!(BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.());
}
// columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
@computed get colHeaderData() {
@@ -65,7 +68,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
if (needsUnsetCategory || colHeaderData === undefined || colHeaderData.length === 0) {
setTimeout(() => {
const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
- const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
if (needsUnsetCategory || columnHeaders.length === 0) {
columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
this.resizeColumns(columnHeaders);
@@ -109,12 +111,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// to render the docs you see within an individual column.
children = (docs: Doc[]) => {
TraceMobx();
- return docs.map((d, i) => {
+ return docs.map(d => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
const style = { width: width(), marginTop: this.gridGap, height: height() };
return (
- <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style}>
+ <div className="collectionNoteTakingView-columnDoc" key={d[Id]} style={style}>
{this.getDisplayDoc(d, width)}
</div>
);
@@ -133,7 +135,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
const rowCol = this.docsDraggedRowCol;
// this will sort the docs into the correct columns (minus the ones you're currently dragging)
- docs.map(d => {
+ docs.forEach(d => {
const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`;
// look for if header exists already
const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
@@ -151,7 +153,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
removeDocDragHighlight = () => {
setTimeout(
- action(() => (this.docsDraggedRowCol.length = 0)),
+ action(() => {
+ this.docsDraggedRowCol.length = 0;
+ }),
100
);
};
@@ -189,13 +193,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
- return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- };
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
- this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
+ this.createDashEventsTarget(ele!);
};
@computed get onChildClickHandler() {
@@ -215,14 +217,15 @@ export class CollectionNoteTakingView extends CollectionSubView() {
Doc.BrushDoc(doc);
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
- const top = found.getBoundingClientRect().top;
+ const { top } = found.getBoundingClientRect();
const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0 && Math.ceil(this._props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
- let focusSpeed = options.zoomTime ?? 500;
+ const focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
return focusSpeed;
}
}
+ return undefined;
};
styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
@@ -237,6 +240,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return this._props.childOpacity();
}
break;
+ default:
}
return this._props.styleProvider?.(doc, props, property);
};
@@ -252,7 +256,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
return (
<DocumentView
- ref={r => (dref = r || undefined)}
+ ref={r => {
+ dref = r || undefined;
+ }}
Document={doc}
TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
pointerEvents={this.blockPointerEventsWhenDragging}
@@ -264,8 +270,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
layout_fitWidth={this._props.childLayoutFitWidth}
isContentActive={emptyFunction}
onKey={this.onKeyDown}
- //TODO: change this from a prop to a parameter passed into a function
- dontHideOnDrag={true}
+ // TODO: change this from a prop to a parameter passed into a function
+ dontHideOnDrag
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
LayoutTemplateString={this._props.childLayoutString}
@@ -299,8 +305,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns
getDocTransform(doc: Doc, dref?: DocumentView) {
- const y = this._scroll; // required for document decorations to update when the text box container is scrolled
- const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
+ this._scroll; // required for document decorations to update when the text box container is scrolled
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv || undefined);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
}
@@ -308,7 +314,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// how to get the width of a document. Currently returns the width of the column (minus margins)
// if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
getDocWidth(d: Doc) {
- const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
+ const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType);
const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0;
const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
@@ -344,7 +350,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33]
@action
resizeColumns = (headers: SchemaHeaderField[]) => {
- const n = headers.length;
const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0);
const scaleFactor = 1 / curWidths;
this.dataDoc[this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(
@@ -382,7 +387,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// we alter the pivot fields of the docs in case they are moved to a new column.
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
- DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader));
+ DragManager.docsBeingDragged
+ .map(doc => doc[DocData])
+ .forEach(d => {
+ d[this.notetakingCategoryField] = colHeader;
+ });
// used to notify sections to re-render
this.docsDraggedRowCol.length = 0;
const columnFromCoord = this.getColumnFromXCoord(xCoord);
@@ -393,7 +402,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate).
// This function is used to know which document a column SHOULD be in while it is being dragged.
getColumnFromXCoord = (xCoord: number): number | undefined => {
- let colIndex: number | undefined = undefined;
+ let colIndex: number | undefined;
const numColumns = this.colHeaderData.length;
const coords = [];
let colStartXCoord = 0;
@@ -416,10 +425,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const docsMatchingHeader: Doc[] = [];
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
- this.childDocs?.map(d => {
+ this.childDocs?.forEach(d => {
if (d instanceof Promise) return;
const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset';
- if (sectionValue.toString() == colHeader) {
+ if (sectionValue.toString() === colHeader) {
docsMatchingHeader.push(d);
}
});
@@ -435,6 +444,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
FormattedTextBox.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
+ return undefined;
};
// onInternalDrop is used when dragging and dropping a document within the view, such as dragging
@@ -572,6 +582,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
alert('You cannot use an existing column name. Please try a new column name');
return value;
}
+ return undefined;
});
const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
const newColWidth = 1 / (this.numGroupColumns + 1);
@@ -590,17 +601,33 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const subItems: ContextMenuProps[] = [];
subItems.push({
description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`,
- event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate),
+ event: () => {
+ this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate;
+ },
icon: 'computer',
});
subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' });
subItems.push({
description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`,
- event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize),
+ event: () => {
+ this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize;
+ },
icon: 'plus',
});
- subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
- subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
+ subItems.push({
+ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`,
+ event: () => {
+ this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+ },
+ icon: 'plus',
+ });
+ subItems.push({
+ description: 'Clear All',
+ event: () => {
+ this.dataDoc.data = new List([]);
+ },
+ icon: 'times',
+ });
ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
}
};
@@ -625,6 +652,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const sections = Array.from(this.Sections.entries());
return sections.reduce((list, sec, i) => {
list.push(this.sectionNoteTaking(sec[0], sec[1]));
+ // eslint-disable-next-line react/no-array-index-key
i !== sections.length - 1 && list.push(<CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
return list;
}, [] as JSX.Element[]);
@@ -658,14 +686,18 @@ export class CollectionNoteTakingView extends CollectionSubView() {
background: this.backgroundColor(),
pointerEvents: this.backgroundEvents,
}}
- onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
- onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
+ onScroll={action(e => {
+ this._scroll = e.currentTarget.scrollTop;
+ })}
+ onPointerLeave={action(() => {
+ this.docsDraggedRowCol.length = 0;
+ })}
onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
- <>{this.renderedSections}</>
+ {this.renderedSections}
<div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
<FieldsDropdown
Document={this.Document}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 448b11b05..95aecc7d0 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,8 +1,9 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { lightOrDark, returnEmptyString } from '../../../Utils';
+import { lightOrDark, returnEmptyString } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
@@ -14,7 +15,7 @@ import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
@@ -113,13 +114,15 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
const castedValue = this.getValue(value);
if (castedValue) {
if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ this._props.docList.forEach(d => {
+ d[this._props.pivotField] = castedValue;
+ });
if (this._props.headingObject) {
this._props.headingObject.setHeading(castedValue.toString());
this._heading = this._props.headingObject.heading;
@@ -129,10 +132,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
return false;
};
- @action pointerEntered = () => (this._hover = true);
- @action pointerLeave = () => (this._hover = false);
- @undoBatch
- addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @action pointerEntered = () => {
+ this._hover = true;
+ };
+ @action pointerLeave = () => {
+ this._hover = false;
+ };
+
+ addTextNote = undoable(() => this.addNewTextDoc('-typed text-', false, true), 'add text note');
// addNewTextDoc is called when a user starts typing in a column to create a new node
@action
@@ -195,6 +202,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
}
return this._props.addDocument?.(created);
}
+ return undefined;
},
icon: 'compress-arrows-alt',
})
@@ -214,6 +222,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
}
return this._props.addDocument?.(created) || false;
}
+ return undefined;
},
icon: 'compress-arrows-alt',
})
@@ -238,7 +247,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
const key = this._props.pivotField;
const heading = this._heading;
const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
- const evContents = heading ? heading : '25';
+ const evContents = heading || '25';
const headingView = this._props.headingObject ? (
<div
key={heading}
@@ -252,17 +261,16 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
className="collectionNoteTakingView-sectionHeader-subCont"
title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
- <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+ <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine />
</div>
{(this._props.colHeaderData?.length ?? 0) > 1 && (
- <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+ <button type="button" className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
)}
</div>
) : null;
const templatecols = this.columnWidth;
- const type = this._props.Document.type;
return (
<>
{headingView}
@@ -270,7 +278,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
<div className="collectionNoteTakingView-columnStack">
<div
key={`${heading}-stack`}
- className={`collectionNoteTakingView-Nodes`}
+ className="collectionNoteTakingView-Nodes"
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
gridGap: this._props.gridGap,
@@ -282,10 +290,13 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
{!this._props.chromeHidden ? (
<div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}>
<div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
- <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} />
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents="+ Node" menuCallback={this.menuCallback} />
</div>
<div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
- <EditableView {...this._props.editableViewProps()} />
+ {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <EditableView {...this._props.editableViewProps()} />
+ }
</div>
</div>
) : null}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 50a97b978..ddd4b8137 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,7 +1,8 @@
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
import { UndoManager } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -27,7 +28,7 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ (moveEv, down, delta) => {
if (!batch) batch = UndoManager.StartBatch('resizing');
this._props.setColumnStartXCoords(delta[0], this._props.index);
return false;
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 9d68c621b..6dfc38e2e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,12 +1,15 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { ScriptField } from '../../../fields/ScriptField';
import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { OpenWhere } from '../nodes/DocumentView';
@@ -66,6 +69,7 @@ export class CollectionPileView extends CollectionSubView() {
return (
<div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
layoutEngine={this.layoutEngine}
addDocument={this.addPileDoc}
@@ -116,16 +120,16 @@ export class CollectionPileView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
+ (moveEv: PointerEvent, down: number[], delta: number[]) => {
+ if (this.layoutEngine() === 'pass' && this.childDocs.length && moveEv.shiftKey) {
dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
if (dist > 100) {
if (!this._undoBatch) {
this._undoBatch = UndoManager.StartBatch('layout pile');
}
const doc = this.childDocs[0];
- doc.x = e.clientX;
- doc.y = e.clientY;
+ doc.x = moveEv.clientX;
+ doc.y = moveEv.clientY;
this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false);
dist = 0;
}
@@ -154,7 +158,7 @@ export class CollectionPileView extends CollectionSubView() {
render() {
return (
- <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
+ <div className="collectionPileView" onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
{this.contents}
</div>
);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 656f850b3..7adf44a5c 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,13 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable no-use-before-define */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -10,7 +16,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -21,7 +27,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
import { LightboxView } from '../LightboxView';
import { AudioWaveform } from '../nodes/audio/AudioWaveform';
import { DocumentView, OpenWhere } from '../nodes/DocumentView';
@@ -57,9 +63,14 @@ export enum TrimScope {
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+ // eslint-disable-next-line no-use-before-define
public static SelectingRegions: Set<CollectionStackedTimeline> = new Set();
public static StopSelecting() {
- this.SelectingRegions.forEach(action(region => (region._selectingRegion = false)));
+ this.SelectingRegions.forEach(
+ action(region => {
+ region._selectingRegion = false;
+ })
+ );
this.SelectingRegions.clear();
}
constructor(props: any) {
@@ -130,7 +141,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
componentWillUnmount() {
document.removeEventListener('keydown', this.keyEvents, true);
if (this._selectingRegion) {
- runInAction(() => (this._selectingRegion = false));
+ runInAction(() => {
+ this._selectingRegion = false;
+ });
CollectionStackedTimeline.SelectingRegions.delete(this);
}
}
@@ -174,9 +187,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
// converts screen pixel offset to time
- toTimeline = (screen_delta: number, width: number) => {
- return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
- };
+ // prettier-ignore
+ toTimeline = (screenDelta: number, width: number) => //
+ Math.max(this.clipStart, Math.min(this.clipEnd, (screenDelta / width) * this.clipDuration + this.clipStart));
@computed get rangeClick() {
// prettier-ignore
@@ -234,6 +247,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
e.stopPropagation();
break;
+ default:
}
}
};
@@ -253,17 +267,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.clientX;
- const diff = rect ? clientX - rect?.x : null;
- const shiftKey = e.shiftKey;
+ const { clientX, shiftKey } = e;
if (rect && this._props.isContentActive()) {
const wasPlaying = this._props.playing();
if (wasPlaying) this._props.Pause();
- var wasSelecting = this._markerEnd !== undefined;
+ let wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(() => {
if (!wasSelecting) {
this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
wasSelecting = true;
@@ -272,8 +284,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
return false;
}),
- action((e, movement, isClick) => {
- this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+ action((upEvent, movement, isClick) => {
+ this._markerEnd = this.toTimeline(upEvent.clientX - rect.x, rect.width);
if (this._markerEnd < this._markerStart) {
const tmp = this._markerStart;
this._markerStart = this._markerEnd;
@@ -286,8 +298,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
this._timelineWrapper && (this._timelineWrapper.style.cursor = '');
}),
- (e, doubleTap) => {
- if (e.button !== 2) {
+ (clickEv, doubleTap) => {
+ if (clickEv.button !== 2) {
this._props.select(false);
!wasPlaying && doubleTap && this._props.Play();
}
@@ -309,7 +321,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
onHover = (e: React.MouseEvent): void => {
e.stopPropagation();
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.clientX;
+ const { clientX } = e;
if (rect) {
this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
if (this.thumbnails) {
@@ -327,14 +339,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action((e, [], []) => {
+ action(moveEv => {
if (rect && this._props.isContentActive()) {
- this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
+ this._trimStart = Math.min(Math.max(this.trimStart + (moveEv.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
}),
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && (this._trimStart = this.clipStart);
})
);
@@ -347,14 +359,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action((e, [], []) => {
+ action(moveEv => {
if (rect && this._props.isContentActive()) {
- this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
+ this._trimEnd = Math.max(Math.min(this.trimEnd + (moveEv.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
}),
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && (this._trimEnd = this.clipEnd);
})
);
@@ -383,7 +395,7 @@ 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) {
+ internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
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.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
@@ -403,7 +415,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0);
+ if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
return false;
};
@@ -441,7 +453,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
@action
- playOnClick = (anchorDoc: Doc, clientX: number) => {
+ playOnClick = (anchorDoc: Doc /* , clientX: number */) => {
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
@@ -450,17 +462,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._props.playFrom(seekTimeInSeconds, endTime);
this.scrollToTime(seekTimeInSeconds);
}
- } else {
- if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
- if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
- this._props.Pause();
- } else {
- this._props.Play();
- }
+ } else if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
+ if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+ this._props.Pause();
} else {
- this._props.playFrom(seekTimeInSeconds, endTime);
- this.scrollToTime(seekTimeInSeconds);
+ this._props.Play();
}
+ } else {
+ this._props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
}
return { select: true };
};
@@ -479,19 +489,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const rect = this._timeline?.getBoundingClientRect();
rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
+ } else if (this.layoutDoc.autoPlayAnchors) {
+ this._props.playFrom(seekTimeInSeconds, endTime);
} else {
- if (this.layoutDoc.autoPlayAnchors) {
- this._props.playFrom(seekTimeInSeconds, endTime);
- } else {
- this._props.setTime(seekTimeInSeconds);
- }
+ this._props.setTime(seekTimeInSeconds);
}
return { select: true };
};
// makes sure no anchors overlaps each other by setting the correct position and width
getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => {
- const timelineContentWidth = this.timelineContentWidth;
+ const { timelineContentWidth } = this;
const x1 = this.anchorStart(m);
const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration);
let max = 0;
@@ -503,6 +511,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
max = Math.max(max, p.level);
return p.level;
}
+ return undefined;
})
);
let level = max + 1;
@@ -564,10 +573,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
onWheel={e => this.isContentActive() && e.stopPropagation()}
onScroll={this.setScroll}
onMouseMove={e => this.isContentActive() && this.onHover(e)}
- ref={wrapper => (this._timelineWrapper = wrapper)}>
+ ref={wrapper => {
+ this._timelineWrapper = wrapper;
+ }}>
<div
className="collectionStackedTimeline"
- ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ ref={(timeline: HTMLDivElement | null) => {
+ this._timeline = timeline;
+ }}
onClick={e => this.isContentActive() && StopEvent(e)}
onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)}
style={{ width: this.timelineContentWidth }}>
@@ -582,7 +595,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const height = this._props.PanelHeight() / maxLevel;
return this.Document.hideAnchors ? null : (
<div
- className={'collectionStackedTimeline-marker-timeline'}
+ className="collectionStackedTimeline-marker-timeline"
key={d.anchor[Id]}
style={{
left,
@@ -592,6 +605,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
pointerEvents: 'none',
}}>
<StackedTimelineAnchor
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
mark={d.anchor}
containerViewPath={this._props.containerViewPath}
@@ -646,7 +660,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
{this.IsTrimming !== TrimScope.None && (
<>
- <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
+ <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} />
<div
className="collectionStackedTimeline-trim-controls"
@@ -654,8 +668,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`,
width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`,
}}>
- <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div>
- <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div>
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft} />
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight} />
</div>
<div
@@ -663,7 +677,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
style={{
left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`,
width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`,
- }}></div>
+ }}
+ />
</>
)}
</div>
@@ -685,7 +700,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
interface StackedTimelineAnchorProps {
mark: Doc;
whenChildContentsActiveChanged: (isActive: boolean) => void;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
rangeClickScript: () => ScriptField;
rangePlayScript: () => ScriptField;
left: number;
@@ -735,13 +750,13 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
this._disposer = reaction(
() => this._props.currentTimecode(),
time => {
- 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);
+ // 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 &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
- /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+ /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */
!this._props.layoutDoc.dontAutoFollowLinks &&
LinkManager.Links(this._props.mark).length &&
time > NumCast(this._props.mark[this._props.startTag]) &&
@@ -763,34 +778,33 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
// starting the drag event for anchor resizing
@action
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
- //this._props._timeline?.setPointerCapture(e.pointerId);
- const newTime = (e: PointerEvent) => {
- const rect = (e.target as any).getBoundingClientRect();
- return this._props.toTimeline(e.clientX - rect.x, rect.width);
+ const newTime = (timeDownEv: PointerEvent) => {
+ const rect = (timeDownEv.target as any).getBoundingClientRect();
+ return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width);
};
- const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
+ const changeAnchor = (time: number | undefined) => {
const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
if (timelineOnly) {
- if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
- Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
- if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
+ const timeMod = !left && time !== undefined && time <= NumCast(anchor[this._props.startTag]) ? undefined : time;
+ Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, timeMod, true);
+ if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', timeMod !== undefined ? undefined : '100%', true);
} else {
anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
}
return false;
};
this.noEvents = true;
- var undo: UndoManager.Batch | undefined;
+ let undo: UndoManager.Batch | undefined;
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
if (!undo) undo = UndoManager.StartBatch('drag anchor');
- this._props.setTime(newTime(e));
- return changeAnchor(anchor, left, newTime(e));
+ this._props.setTime(newTime(moveEv));
+ return changeAnchor(newTime(moveEv));
},
- action(e => {
- this._props.setTime(newTime(e));
+ action(upEv => {
+ this._props.setTime(newTime(upEv));
undo?.end();
this.noEvents = false;
}),
@@ -828,7 +842,9 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
- ref={action((r: DocumentView | null) => (anchor.view = r))}
+ ref={action((r: DocumentView | null) => {
+ anchor.view = r;
+ })}
Document={mark}
TemplateDataDocument={undefined}
containerViewPath={this._props.containerViewPath}
@@ -851,7 +867,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
onClickScript={script}
onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
- hideResizeHandles={true}
+ hideResizeHandles
contextMenuItems={this.contextMenuItems}
/>
),
@@ -877,12 +893,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function formatToTime(time: number): any {
return formatTime(time);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function min(num1: number, num2: number): number {
return Math.min(num1, num2);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function max(num1: number, num2: number): number {
return Math.max(num1, num2);
});
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bf0393883..b79d660c6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as CSS from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { ClientUtils, DivHeight, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -11,10 +13,11 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,6 +34,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
import { CollectionSubView } from './CollectionSubView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionStackingViewProps = {
@@ -125,11 +129,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// TODO: plj - these are the children
children = (docs: Doc[]) => {
- //TODO: can somebody explain me to what exactly TraceMobX is?
+ // TODO: can somebody explain me to what exactly TraceMobX is?
TraceMobx();
// appears that we are going to reset the _docXfs. TODO: what is Xfs?
this._docXfs.length = 0;
- this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5))));
+ this._renderCount < docs.length &&
+ setTimeout(
+ action(() => {
+ this._renderCount = Math.min(docs.length, this._renderCount + 5);
+ })
+ );
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
@@ -152,19 +161,21 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
};
// is sections that all collections inherit? I think this is how we show the masonry/columns
- //TODO: this seems important
+ // TODO: this seems important
get Sections() {
// appears that pivot field IS actually for sorting
if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
if (this.colHeaderData === undefined) {
- setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
+ setTimeout(() => {
+ this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
+ });
return new Map<SchemaHeaderField, Doc[]>();
}
const colHeaderData = Array.from(this.colHeaderData);
const fields = new Map<SchemaHeaderField, Doc[]>(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []]));
let changed = false;
- this.filteredChildren.map(d => {
+ this.filteredChildren.forEach(d => {
const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
// the next five lines ensures that floating point rounding errors don't create more than one section -syip
const parsed = parseInt(sectionValue.toString());
@@ -186,7 +197,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (this.layoutDoc._columnsHideIfEmpty) {
Array.from(fields.keys())
.filter(key => !fields.get(key)!.length)
- .map(header => {
+ .forEach(header => {
fields.delete(header);
colHeaderData.splice(colHeaderData.indexOf(header), 1);
changed = true;
@@ -207,11 +218,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// reset section headers when a new filter is inputted
this._disposers.pivotField = reaction(
() => this.pivotField,
- () => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
+ () => {
+ this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List();
+ }
);
this._disposers.autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
+ layoutAutoHeight => layoutAutoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
);
this._disposers.refList = reaction(
() => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
@@ -231,9 +244,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
isAnyChildContentActive = () => this._props.isAnyChildContentActive();
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- };
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
@@ -250,10 +261,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
- const top = found.getBoundingClientRect().top;
+ const { top } = found.getBoundingClientRect();
const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- let focusSpeed = options.zoomTime ?? 500;
+ const focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
return focusSpeed;
}
@@ -276,18 +287,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if (['Enter'].includes(e.key) && e.ctrlKey) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== 'Tab';
- const layout_fieldKey = StrCast(fieldProps.fieldKey);
+ const layoutFieldKey = StrCast(fieldProps.fieldKey);
const newDoc = Doc.MakeCopy(fieldProps.Document, true);
const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
+ if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
+ newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
}
newDoc[DocData].text = undefined;
FormattedTextBox.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
+ return false;
};
isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
@@ -363,7 +374,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
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);
+ const { translateX, translateY } = ClientUtils.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.ScreenToLocalBoxXf().Scale);
}
@@ -399,7 +410,9 @@ 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'));
+ runInAction(() => {
+ this._cursor = 'grabbing';
+ });
const batch = UndoManager.StartBatch('stacking width');
setupMoveUpEvents(
this,
@@ -425,7 +438,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onPointerDown={this.columnDividerDown}
ref={this._draggerRef}
style={{ cursor: this._cursor, color: SettingsManager.userColor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
- <FontAwesomeIcon icon={'arrows-alt-h'} />
+ <FontAwesomeIcon icon="arrows-alt-h" />
</div>
);
}
@@ -439,7 +452,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
let dropAfter = 0;
if (de.complete.docDragData) {
// going to re-add the docs to the _docXFs based on position of where we just dropped
- this._docXfs.map((cd, i) => {
+ this._docXfs.forEach((cd, i) => {
const pos = cd
.stackedDocTransform()
.inverse()
@@ -496,7 +509,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const where = [e.clientX, e.clientY];
let targInd = -1;
- this._docXfs.map((cd, i) => {
+ this._docXfs.forEach((cd, i) => {
const pos = cd
.stackedDocTransform()
.inverse()
@@ -521,10 +534,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
- let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
if (this.pivotField) {
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ // eslint-disable-next-line prefer-destructuring
type = types[0];
}
}
@@ -557,9 +571,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// what a section looks like if we're in masonry. Shouldn't actually need to use this.
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
const key = this.pivotField;
- let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ // eslint-disable-next-line prefer-destructuring
type = types[0];
}
const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
@@ -609,9 +624,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
- optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
- optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+ optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => { this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill; }, icon: 'plus' }); // prettier-ignore
+ optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => { this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; }, icon: 'plus' }); // prettier-ignore
+ optionItems.push({ description: 'Clear All', event: () => { this.dataDoc[this.fieldKey ?? 'data'] = new List([]); } , icon: 'times' }); // prettier-ignore
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
}
};
@@ -700,7 +715,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
ref={ele => {
this._masonryGridRef = ele;
- this.createDashEventsTarget(ele); //so the whole grid is the drop target?
+ this.createDashEventsTarget(ele); // so the whole grid is the drop target?
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = ele;
// prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
@@ -711,7 +726,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents,
}}
- onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
+ onScroll={action(e => {
+ this._scroll = e.currentTarget.scrollTop;
+ })}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this.isContentActive() && e.stopPropagation()}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c5292f880..cb463077c 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,7 +1,11 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DivHeight, DivWidth, returnEmptyString, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -9,10 +13,11 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -95,7 +100,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._ele && this.props.refList.push(this._ele);
this._disposers.collapser = reaction(
() => this._props.headingObject?.collapsed,
- collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
+ collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore
{ fireImmediately: true }
);
}
@@ -105,7 +110,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._ele = null;
}
- //TODO: what is scripting? I found it in SetInPlace def but don't know what that is
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
@@ -121,13 +125,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
const castedValue = this.getValue(value);
if (castedValue) {
if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ this._props.pivotField && this._props.docList.forEach(d => { d[this._props.pivotField] = castedValue; }) // prettier-ignore
if (this._props.headingObject) {
this._props.headingObject.setHeading(castedValue.toString());
this._heading = this._props.headingObject.heading;
@@ -143,9 +147,9 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._color = color;
};
- @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
- @action pointerLeave = () => (this._background = 'inherit');
- @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @action pointerEntered = () => { SnappingManager.IsDragging && (this._background = '#b4b4b4'); } // prettier-ignore
+ @action pointerLeave = () => { this._background = 'inherit'}; // prettier-ignore
+ @undoBatch typedNote = () => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -153,7 +157,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
key && (newDoc[key] = this.getValue(this._props.heading));
- const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+ const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0);
const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
FormattedTextBox.SetSelectOnLoad(newDoc);
@@ -163,7 +167,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
@action
deleteColumn = () => {
- this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+ this._props.pivotField &&
+ this._props.docList.forEach(d => {
+ d[this._props.pivotField] = undefined;
+ });
if (this._props.colHeaderData && this._props.headingObject) {
const index = this._props.colHeaderData.indexOf(this._props.headingObject);
this._props.colHeaderData.splice(index, 1);
@@ -178,8 +185,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
- //TODO: I think this is where I'm supposed to edit stuff
- startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
+ // TODO: I think this is where I'm supposed to edit stuff
+ startDrag = (e: PointerEvent) => {
// is MakeEmbedding a way to make a copy of a doc without rendering it?
const embedding = Doc.MakeEmbedding(this._props.Document);
embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
@@ -210,23 +217,21 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
);
};
- renderMenu = () => {
- return (
- <div className="collectionStackingView-optionPicker">
- <div className="optionOptions">
- <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
- Add options here
- </div>
- </div>
+ renderMenu = () => (
+ <div className="collectionStackingView-optionPicker">
+ <div className="optionOptions">
+ <div className="optionPicker active">Add options here</div>
</div>
- );
- };
+ </div>
+ );
@observable private collapsed: boolean = false;
- private toggleVisibility = action(() => (this.collapsed = !this.collapsed));
+ private toggleVisibility = action(() => {
+ this.collapsed = !this.collapsed;
+ });
- menuCallback = (x: number, y: number) => {
+ menuCallback = () => {
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
@@ -257,6 +262,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
}
return this._props.addDocument?.(created);
}
+ return false;
},
icon: 'compress-arrows-alt',
})
@@ -276,6 +282,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
}
return this._props.addDocument?.(created) || false;
}
+ return false;
},
icon: 'compress-arrows-alt',
})
@@ -307,7 +314,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
- const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+ const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader);
const headingView = this._props.headingObject ? (
<div
key={heading}
@@ -324,14 +331,19 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
onPointerDown={this.headerDown}
title={evContents === noValueHeader ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
style={{ background: evContents !== noValueHeader ? this._color : 'inherit' }}>
- <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+ <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />
<div className="collectionStackingView-sectionColor" style={{ display: evContents === noValueHeader ? 'none' : undefined }}>
- <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}>
+ <button
+ type="button"
+ className="collectionStackingView-sectionColorButton"
+ onClick={action(() => {
+ this._paletteOn = !this._paletteOn;
+ })}>
<FontAwesomeIcon icon="palette" size="lg" />
</button>
{this._paletteOn ? this.renderColorPicker() : null}
</div>
- <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+ <button type="button" className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
{/* {evContents === noValueHeader ? null : (
@@ -352,7 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
</div>
) : null;
const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
- const type = this._props.Document.type;
+ const { type } = this._props.Document;
return (
<>
{this._props.Document._columnsHideIfEmpty ? null : headingView}
@@ -364,7 +376,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: 'auto',
- width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
gridGap: this._props.gridGap,
@@ -376,10 +388,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
{!this._props.chromeHidden && type !== DocumentType.PRES ? (
// TODO: this is the "new" button: see what you can work with here
// change cursor to pointer for this, and update dragging cursor
- //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
- //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
+ // TODO: there is a bug that occurs when adding a freeform document and trying to move it around
+ // TODO: would be great if there was additional space beyond the frame, so that you can actually see your
// bottom note
- //TODO: ok, so we are using a single column, and this is it!
+ // TODO: ok, so we are using a single column, and this is it!
<div
key={`${heading}-add-document`}
onKeyDown={e => e.stopPropagation()}
@@ -390,7 +402,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
SetValue={this.addNewTextDoc}
textCallback={this.typedNote}
placeholder={"Type ':' for commands"}
- contents={<FontAwesomeIcon icon={'plus'} />}
+ contents={<FontAwesomeIcon icon="plus" />}
menuCallback={this.menuCallback}
/>
</div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32198e3a2..f3dedaedf 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,13 +1,14 @@
import { action, computed, makeObservable, observable } from 'mobx';
import * as React from 'react';
import * as rp from 'request-promise';
-import { Utils, returnFalse } from '../../../Utils';
+import { ClientUtils, returnFalse } from '../../../ClientUtils';
import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, DocData } from '../../../fields/DocSymbols';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { AclPrivate } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
@@ -15,23 +16,54 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { ViewBoxBaseComponent } from '../DocComponent';
import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { FieldViewProps } from '../nodes/FieldView';
import { LoadingBox } from '../nodes/LoadingBox';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { CollectionView, CollectionViewProps } from './CollectionView';
+
+export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
+ isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
+ isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
+ layoutEngine?: () => string;
+ setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
+ ignoreUnrendered?: boolean;
+
+ // property overrides for child documents
+ childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
+ childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childContentsActive?: () => boolean | undefined;
+ childLayoutFitWidth?: (child: Doc) => boolean;
+ childlayout_showTitle?: () => string;
+ childOpacity?: () => number;
+ childContextMenuItems?: () => { script: ScriptField; label: string }[];
+ childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+ childHideDecorationTitle?: boolean;
+ childHideResizeHandles?: boolean;
+ childDragAction?: dropActionType;
+ childXPadding?: number;
+ childYPadding?: number;
+ childLayoutString?: string;
+ childIgnoreNativeSize?: boolean;
+ childClickScript?: ScriptField;
+ childDoubleClickScript?: ScriptField;
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
+ hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
+}
export interface SubCollectionViewProps extends CollectionViewProps {
isAnyChildContentActive: () => boolean;
}
-export function CollectionSubView<X>(moreProps?: X) {
- class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
+export function CollectionSubView<X>() {
+ class CollectionSubViewInternal extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected _mainCont?: HTMLDivElement;
@@ -53,7 +85,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
};
protected CreateDropTarget(ele: HTMLDivElement) {
- //used in schema view
+ // used in schema view
this.createDashEventsTarget(ele);
}
@@ -83,10 +115,11 @@ export function CollectionSubView<X>(moreProps?: X) {
const { Document, TemplateDataDocument } = this._props;
const validPairs = this.childDocs
.map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
- .filter(pair => {
- // filter out any documents that have a proto that we don't have permissions to
- return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
- });
+ .filter(
+ pair =>
+ // filter out any documents that have a proto that we don't have permissions to
+ !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))
+ );
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
@computed get childDocList() {
@@ -95,9 +128,9 @@ export function CollectionSubView<X>(moreProps?: X) {
collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
// child filters apply to the descendants of the documents in this collection
- childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+ childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
// unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack
- unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+ unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])];
childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
@computed.struct get childDocs() {
@@ -129,29 +162,30 @@ export function CollectionSubView<X>(moreProps?: X) {
const docsforFilter: Doc[] = [];
childDocs.forEach(d => {
// dragging facets
- const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
- if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
+ const dragged = this._props.childFilters?.().some(f => f.includes(ClientUtils.noDragDocsFilter));
+ if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return;
let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
if (notFiltered) {
notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
- const data = d[annos ? fieldKey + '_annotations' : fieldKey];
- const side = annos && d[fieldKey + '_sidebar'];
- if (data !== undefined || side !== undefined) {
- let subDocs = [...DocListCast(data), ...DocListCast(side)];
+ const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
+ const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
+ const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
+ if (docChildDocs !== undefined || sidebarDocs !== undefined) {
+ let subDocs = [...DocListCast(docChildDocs), ...DocListCast(sidebarDocs)];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, childFiltersByRanges, d).length);
while (subDocs.length > 0 && !notFiltered) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
- const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
+ const docFieldKey = Doc.LayoutFieldKey(t);
+ const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
notFiltered =
notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
- DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
- annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
+ DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
+ isSubDocAnnotatable && DocListCast(t[docFieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
subDocs = newarray;
}
@@ -168,7 +202,7 @@ export function CollectionSubView<X>(moreProps?: X) {
let ind;
const doc = this.Document;
const id = Doc.UserDoc()[Id];
- const email = Doc.CurrentUserEmail;
+ const email = ClientUtils.CurrentUserEmail();
const pos = { x: position[0], y: position[1] };
if (id && email) {
const proto = Doc.GetProto(doc);
@@ -184,6 +218,7 @@ export function CollectionSubView<X>(moreProps?: X) {
if (!cursors) {
proto.cursors = cursors = new List<CursorField>();
}
+ // eslint-disable-next-line no-cond-assign
if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
cursors[ind].setPosition(pos);
} else {
@@ -194,6 +229,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@undoBatch
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) {
@@ -212,12 +248,12 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData) {
- let added = undefined;
+ let added;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
@@ -245,8 +281,9 @@ export function CollectionSubView<X>(moreProps?: X) {
}
added === false && !this._props.isAnnotationOverlay && e.preventDefault();
added === true && e.stopPropagation();
- return added ? true : false;
- } else if (de.complete.annoDragData) {
+ return !!added;
+ }
+ if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
@@ -316,7 +353,7 @@ export function CollectionSubView<X>(moreProps?: X) {
const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: [imgSrc] })).lastElement();
const newImgSrc =
result.accessPaths.agnostic.client.indexOf('dashblobstore') === -1 //
- ? Utils.prepend(result.accessPaths.agnostic.client)
+ ? ClientUtils.prepend(result.accessPaths.agnostic.client)
: result.accessPaths.agnostic.client;
addDocument(ImageUtils.AssignImgInfo(Docs.Create.ImageDocument(newImgSrc, imgOpts), result));
@@ -325,39 +362,39 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument(ImageUtils.AssignImgInfo(doc, await ImageUtils.ExtractImgInfo(doc)));
}
return;
+ }
+ const path = window.location.origin + '/doc/';
+ if (text.startsWith(path)) {
+ const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
+ DocServer.GetRefField(docId).then(f => {
+ const fDoc = f;
+ if (fDoc instanceof Doc) {
+ if (options.x || options.y) {
+ fDoc.x = options.x as number;
+ fDoc.y = options.y as number;
+ } // should be in CollectionFreeFormView
+ addDocument(fDoc);
+ }
+ });
} else {
- const path = window.location.origin + '/doc/';
- if (text.startsWith(path)) {
- const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
- DocServer.GetRefField(docId).then(f => {
- if (f instanceof Doc) {
- if (options.x || options.y) {
- f.x = options.x as number;
- f.y = options.y as number;
- } // should be in CollectionFreeFormView
- f instanceof Doc && addDocument(f);
- }
- });
- } else {
- const srcWeb = SelectionManager.Views.lastElement();
- const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
- const reg = new RegExp(Utils.prepend(''), 'g');
- const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
- const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
- const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
- Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
- addDocument(htmlDoc);
- if (srcWeb) {
- const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
- const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
- if (focusNode) {
- const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
- anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
- }
+ const srcWeb = SelectionManager.Views.lastElement();
+ const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
+ const reg = new RegExp(ClientUtils.prepend(''), 'g');
+ const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
+ const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
+ Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
+ addDocument(htmlDoc);
+ if (srcWeb) {
+ const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+ const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
+ if (focusNode) {
+ const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
+ anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
}
}
- return;
}
+ return;
}
}
@@ -414,10 +451,15 @@ export function CollectionSubView<X>(moreProps?: X) {
for (let i = 0; i < length; i++) {
const item = e.dataTransfer.items[i];
if (item.kind === 'string' && item.type.includes('uri')) {
- const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
- const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type'];
+ // eslint-disable-next-line no-await-in-loop
+ const stringContents = await new Promise<string>(resolve => {
+ item.getAsString(resolve);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ const type = (await rp.head(ClientUtils.CorsProxy(stringContents)))['content-type'];
if (type) {
- const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options);
+ // eslint-disable-next-line no-await-in-loop
+ const doc = await DocUtils.DocumentFromType(type, ClientUtils.CorsProxy(stringContents), options);
doc && generatedDocuments.push(doc);
}
}
@@ -426,7 +468,7 @@ export function CollectionSubView<X>(moreProps?: X) {
file?.type && files.push(file);
file?.type === 'application/json' &&
- Utils.readUploadedFileAsText(file).then(result => {
+ ClientUtils.readUploadedFileAsText(file).then(result => {
const json = JSON.parse(result as string);
addDocument(
Docs.Create.TreeDocument(
@@ -450,7 +492,7 @@ export function CollectionSubView<X>(moreProps?: X) {
// create placeholder docs
// inside placeholder docs have some func that
- let pileUpDoc = undefined;
+ let pileUpDoc;
if (typeof files === 'string') {
const loading = Docs.Create.LoadingDocument(files, options);
generatedDocuments.push(loading);
@@ -478,23 +520,19 @@ export function CollectionSubView<X>(moreProps?: X) {
})
: [];
if (completed) completed(set);
- else {
- if (isFreeformView && generatedDocuments.length > 1) {
- pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
- addDocument(pileUpDoc);
- } else {
- generatedDocuments.forEach(addDocument);
- }
- }
- } else {
- if (text && !text.includes('https://')) {
- addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+ else if (isFreeformView && generatedDocuments.length > 1) {
+ pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
+ addDocument(pileUpDoc);
} else {
- alert('Document upload failed - possibly an unsupported file type.');
+ generatedDocuments.forEach(addDocument);
}
+ } else if (text && !text.includes('https://')) {
+ addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+ } else {
+ alert('Document upload failed - possibly an unsupported file type.');
}
};
}
- return CollectionSubView;
+ return CollectionSubViewInternal;
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 37452ddfb..e1d2e3c40 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -15,7 +18,6 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { FieldsDropdown } from '../FieldsDropdown';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
import { PresBox } from '../nodes/trails';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
@@ -67,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => {
+ scrollPreview = (docView: DocumentView, anchor: Doc /* , focusSpeed: number, options: FocusViewOptions */) => {
// if in preview, then override document's fields with view spec
this._focusFilters = StrListCast(anchor.config_docFilters);
this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
@@ -76,13 +78,15 @@ export class CollectionTimeView extends CollectionSubView() {
};
layoutEngine = () => this._layoutEngine;
- toggleVisibility = action(() => (this._collapsed = !this._collapsed));
+ toggleVisibility = action(() => {
+ this._collapsed = !this._collapsed;
+ });
onMinDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -98,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -113,7 +117,7 @@ export class CollectionTimeView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -133,7 +137,7 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- contentsDown = (e: React.MouseEvent) => {
+ contentsDown = () => {
const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
if (prevFilterIndex > 0) {
this.goTo(prevFilterIndex - 1);
@@ -146,6 +150,7 @@ export class CollectionTimeView extends CollectionSubView() {
return (
<div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
fitContentsToBox={returnTrue}
@@ -161,7 +166,7 @@ export class CollectionTimeView extends CollectionSubView() {
const fieldKey = Doc.LayoutFieldKey(doc);
doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
}
- specificMenu = (e: React.MouseEvent) => {
+ specificMenu = () => {
const layoutItems: ContextMenuProps[] = [];
const doc = this.layoutDoc;
@@ -193,9 +198,9 @@ export class CollectionTimeView extends CollectionSubView() {
render() {
let nonNumbers = 0;
- this.childDocs.map(doc => {
+ this.childDocs.forEach(doc => {
const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
- if (Number.isNaN(num)) {
+ if (isNaN(num)) {
nonNumbers++;
}
});
@@ -225,13 +230,20 @@ export class CollectionTimeView extends CollectionSubView() {
</>
)}
<div style={{ right: 0, top: 0, position: 'absolute' }}>
- <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} />
+ <FieldsDropdown
+ Document={this.Document}
+ selectFunc={fieldKey => {
+ this.layoutDoc._pivotField = fieldKey;
+ }}
+ placeholder={StrCast(this.layoutDoc._pivotField)}
+ />
</div>
</div>
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
const pivotField = StrCast(pivotDoc._pivotField, 'author');
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
@@ -248,6 +260,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+ // eslint-disable-next-line prefer-destructuring
pivotDoc._pivotField = filterVals[0];
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5741fc29b..bf81bdc7f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,6 +1,9 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -8,10 +11,11 @@ import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
+import { emptyFunction, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -21,13 +25,13 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -92,8 +96,10 @@ 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) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ });
+ isContentActive = () => (this._isAnyChildContentActive ? true : !!this._props.isContentActive());
componentWillUnmount() {
this._isDisposing = true;
@@ -103,7 +109,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
componentDidMount() {
- //this._props.setContentView?.(this);
+ // this._props.setContentView?.(this);
this._disposers.autoheight = reaction(
() => this.layoutDoc.layout_autoHeight,
auto => auto && this.computeHeight(),
@@ -126,20 +132,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
observeHeight = (ref: any) => {
if (ref) {
this.refList.add(ref);
- this.observer = new _global.ResizeObserver(
- action((entries: any) => {
- if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
- this.computeHeight();
- }
- })
- );
+ this.observer = new _global.ResizeObserver(() => {
+ if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
+ this.computeHeight();
+ }
+ });
this.layoutDoc.layout_autoHeight && this.computeHeight();
this.observer.observe(ref);
}
};
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
+ this._mainEle = ele;
+ if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
};
protected onInternalDrop(e: Event, de: DragManager.DropEvent) {
@@ -167,39 +172,39 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
};
- dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document);
+ dragConfig = (dragData: DragManager.DocumentDragData) => { dragData.treeViewDoc = this.Document; }; // prettier-ignore
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
@action
- remove = (doc: Doc | Doc[]): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc;
+ remove = (docIn: Doc | Doc[]): boolean => {
+ const docs = docIn instanceof Doc ? [docIn] : docIn;
const targetDataDoc = this.Document[DocData];
const value = DocListCast(targetDataDoc[this._props.fieldKey]);
const result = value.filter(v => !docs.includes(v));
- if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
+ if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
if (result.length !== value.length) {
- if (doc instanceof Doc) {
- const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+ if (docIn instanceof Doc) {
+ const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn);
const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
- this._props.removeDocument?.(doc);
+ this._props.removeDocument?.(docIn);
if (ind > 0 && prev) {
FormattedTextBox.SetSelectOnLoad(prev);
DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
}
return true;
}
- return this._props.removeDocument?.(doc) ?? false;
+ return this._props.removeDocument?.(docIn) ?? false;
}
return false;
};
@action
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
- const doclist = docs instanceof Doc ? [docs] : docs;
- const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
+ const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
if (this.Document.resolvedDataDoc instanceof Promise) return false;
- const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs);
+ const doclist = docs instanceof Doc ? [docs] : docs;
+ const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist);
res &&
doclist.forEach(doc => {
Doc.SetContainer(doc, this.Document);
@@ -207,7 +212,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
});
return res;
};
- onContextMenu = (e: React.MouseEvent): void => {
+ onContextMenu = (): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
const layoutItems: ContextMenuProps[] = [];
const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc;
@@ -215,11 +220,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if (!Doc.noviceMode) {
layoutItems.push({
description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
- event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+ event: () => { this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient; }, // prettier-ignore
icon: 'paint-brush',
});
- layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
- layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => { this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields; }, icon: 'paint-brush' }); // prettier-ignore
+ layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => { this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle; }, icon: 'paint-brush' }); // prettier-ignore
}
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
if (!Doc.noviceMode) {
@@ -238,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<EditableView
contents={this.dataDoc.title}
- display={'block'}
+ display="block"
maxHeight={72}
- height={'auto'}
+ height="auto"
GetValue={() => StrCast(this.dataDoc.title)}
SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
@@ -251,22 +256,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
- onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => {
if (this.outlineMode && e.key === 'Enter') {
e.stopPropagation();
this.makeTextCollection(this.treeChildren);
return true;
}
+ return undefined;
};
get documentTitle() {
return (
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey="text"
renderDepth={this._props.renderDepth + 1}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
- forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
+ forceAutoHeight // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
PanelWidth={this.documentTitleWidth}
PanelHeight={this.documentTitleHeight}
NativeDimScaling={returnOne}
@@ -292,9 +299,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@computed get treeViewElements() {
TraceMobx();
const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
- const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+ const treeAddDoc = (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))));
+ if (this._renderCount < this.treeChildren.length)
+ setTimeout(
+ action(() => {
+ this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20);
+ })
+ );
return TreeView.GetChildElements(
this.treeChildren,
this,
@@ -303,7 +315,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this._props.TemplateDataDocument,
undefined,
undefined,
- addDoc,
+ treeAddDoc,
this.remove,
moveDoc,
dragAction,
@@ -324,7 +336,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this.observeHeight,
this.unobserveHeight,
this.childContextMenuItems(),
- //TODO: [AL] add these
this._props.AddToMap,
this._props.RemFromMap,
this._props.hierarchyIndex,
@@ -335,7 +346,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return this.dataDoc === null ? null : (
<div
className="collectionTreeView-titleBar"
- ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale))}
+ ref={action((r: any) => {
+ (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale);
+ })}
key={this.Document[Id]}
style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
@@ -406,8 +419,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
- moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
- this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
+ moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false;
@observable _headerHeight = 0;
@computed get content() {
@@ -418,7 +430,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
- <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}>
+ <div
+ className="documentButtonMenu"
+ ref={action((r: HTMLDivElement | null) => {
+ r && (this._headerHeight = DivHeight(r));
+ })}>
{this.buttonMenu}
{this.noviceExplainer}
</div>
@@ -451,7 +467,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
minHeight: '100%',
}}
onWheel={e => e.stopPropagation()}
- onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
+ onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
onDrop={this.onTreeDrop}>
<ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
</div>
@@ -468,13 +484,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
{!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
- isAnnotationOverlay={true}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlay
+ isAnnotationOverlayScrollable
childDocumentsActive={this._props.isContentActive}
fieldKey={this._props.fieldKey + '_annotations'}
dropAction={dropActionType.move}
@@ -498,6 +515,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addTreeFolder(doc: Doc) {
CollectionTreeView.addTreeFolder(doc);
});
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 18eb4dd1f..a0d84ab28 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,21 +1,20 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnEmptyString } from '../../../Utils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { returnEmptyString } from '../../../ClientUtils';
+import { Doc, DocListCast } from '../../../fields/Doc';
import { ObjectField } from '../../../fields/ObjectField';
-import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DocUtils } from '../../documents/Documents';
-import { dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { OpenWhere } from '../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView } from '../nodes/FieldView';
import { CollectionCalendarView } from './CollectionCalendarView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
@@ -23,7 +22,7 @@ import { CollectionDockingView } from './CollectionDockingView';
import { CollectionNoteTakingView } from './CollectionNoteTakingView';
import { CollectionPileView } from './CollectionPileView';
import { CollectionStackingView } from './CollectionStackingView';
-import { SubCollectionViewProps } from './CollectionSubView';
+import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
@@ -33,36 +32,7 @@ import { CollectionLinearView } from './collectionLinear';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
- isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
- isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
- layoutEngine?: () => string;
- setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
- ignoreUnrendered?: boolean;
- // property overrides for child documents
- childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
- childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
- childContentsActive?: () => boolean | undefined;
- childLayoutFitWidth?: (child: Doc) => boolean;
- childlayout_showTitle?: () => string;
- childOpacity?: () => number;
- childContextMenuItems?: () => { script: ScriptField; label: string }[];
- childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
- childHideDecorationTitle?: boolean;
- childHideResizeHandles?: boolean;
- childDragAction?: dropActionType;
- childXPadding?: number;
- childYPadding?: number;
- childLayoutString?: string;
- childIgnoreNativeSize?: boolean;
- childClickScript?: ScriptField;
- childDoubleClickScript?: ScriptField;
- //TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
- hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
-}
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldStr: string) {
@@ -89,7 +59,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
// this a reaction, downstream invalidations only occur when the reaction value actually changes.
this.reactionDisposer = reaction(
() => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
- active => (this._isContentActive = active),
+ active => {
+ this._isContentActive = active;
+ },
{ fireImmediately: true }
);
}
@@ -100,13 +72,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.layoutDoc._type_collection);
if (CollectionView._safeMode) {
- switch (viewField) {
+ switch (viewField) {
case CollectionViewType.Freeform:
- case CollectionViewType.Schema:
- return CollectionViewType.Tree;
- case CollectionViewType.Invalid:
- return CollectionViewType.Freeform;
- }
+ case CollectionViewType.Schema: return CollectionViewType.Tree;
+ case CollectionViewType.Invalid: return CollectionViewType.Freeform;
+ default:
+ } // prettier-ignore
}
return viewField as any as CollectionViewType;
}
@@ -117,8 +88,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
TraceMobx();
if (type === undefined) return null;
switch (type) {
- default:
- case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />;
case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
@@ -134,6 +103,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />;
case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />;
case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />;
+ case CollectionViewType.Freeform:
+ default: return <CollectionFreeFormView key="collview" {...props} />;
}
};
@@ -144,9 +115,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
{ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' },
{ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' },
{ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' },
- { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Stacking', event: () => {func(CollectionViewType.Stacking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
{ description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'},
- { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Notetaking', event: () => {func(CollectionViewType.NoteTaking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
{ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
{ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' },
{ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' },
@@ -178,14 +149,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null;
+ !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore
if (this.Document.childLayout instanceof Doc) {
optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
if (this.Document.childClickedOpenTemplateView instanceof Doc) {
optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
- !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' });
+ !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
@@ -200,7 +171,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
onClicks.push({
description: `Edit ${func.name} script`,
icon: 'edit',
- event: (obj: any) => {
+ event: () => {
const embedding = Doc.MakeEmbedding(this.Document);
DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
this._props.addDocTab(embedding, OpenWhere.addRight);
@@ -211,7 +182,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
onClicks.push({
description: `Set child ${childClick.title}`,
icon: 'edit',
- event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
+ event: () => {
+ this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
+ },
})
);
!Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
@@ -229,7 +202,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
bodyPanelWidth = () => this._props.PanelWidth();
childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
- isContentActive = (outsideReaction?: boolean) => this._isContentActive;
+ isContentActive = () => this._isContentActive;
pointerEvents = () =>
this.layoutDoc._lockedPosition && //
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 4523a4f1e..7dc08389b 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
import { observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -35,11 +36,36 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
return (
<div className="collectionViewBaseChrome-viewSpecsMenu-row">
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
- <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+ <input
+ className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+ value={this._key}
+ onChange={e =>
+ runInAction(() => {
+ this._key = e.target.value;
+ })
+ }
+ placeholder="KEY"
+ />
+ <button
+ className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+ style={{ background: this._contains ? '#77dd77' : '#ff6961' }}
+ onClick={() =>
+ runInAction(() => {
+ this._contains = !this._contains;
+ })
+ }>
{this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
</button>
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
+ <input
+ className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+ value={this._value}
+ onChange={e =>
+ runInAction(() => {
+ this._value = e.target.value;
+ })
+ }
+ placeholder="VALUE"
+ />
</div>
);
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 5cb7b149b..a661cf6a2 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,7 +6,8 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { DashColor, Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
+import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -18,28 +19,169 @@ import { DocServer } from '../../DocServer';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { UndoManager, undoable } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
+import { PinProps } from '../DocComponent';
import { LightboxView } from '../LightboxView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { Colors } from '../global/globalEnums';
import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
+import { FieldViewProps } from '../nodes/FieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
-import { DashFieldView } from '../nodes/formattedText/DashFieldView';
-import { PinProps, PresBox, PresMovement } from '../nodes/trails';
+import { PresBox, PresMovement } from '../nodes/trails';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+
const _global = (window /* browser */ || global) /* node */ as any;
+interface TabMinimapViewProps {
+ document: Doc;
+ tabView: () => DocumentView | undefined;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ background: () => string;
+}
+interface TabMiniThumbProps {
+ miniWidth: () => number;
+ miniHeight: () => number;
+ miniTop: () => number;
+ miniLeft: () => number;
+}
+
+@observer
+class TabMiniThumb extends React.Component<TabMiniThumbProps> {
+ render() {
+ const { miniWidth, miniHeight, miniLeft, miniTop } = this.props;
+ return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />;
+ }
+}
+@observer
+export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
+ static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
+ if (doc) {
+ switch (property.split(':')[0]) {
+ case StyleProp.PointerEvents: return 'none';
+ case StyleProp.DocContents: {
+ const background = (() => {
+ switch (doc.type as DocumentType) {
+ case DocumentType.PDF: return 'pink';
+ case DocumentType.AUDIO: return 'lightgreen';
+ case DocumentType.WEB: return 'brown';
+ case DocumentType.IMG: return 'blue';
+ case DocumentType.MAP: return 'orange';
+ case DocumentType.VID: return 'purple';
+ case DocumentType.RTF: return 'yellow';
+ case DocumentType.COL: return undefined;
+ default: return 'gray';
+ } // prettier-ignore
+ })();
+ return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
+ }
+ default: return DefaultStyleProvider(doc, props, property);
+ } // prettier-ignore
+ }
+ return undefined;
+ };
+
+ @computed get renderBounds() {
+ const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
+ const bounds = compView?.freeformData?.(true)?.bounds;
+ if (!bounds) return undefined;
+ const xbounds = bounds.r - bounds.x;
+ const ybounds = bounds.b - bounds.y;
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
+ }
+ @computed get xPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+ }
+ @computed get yPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+ }
+ childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ const doc = this._props.document;
+ const miniSize = this.returnMiniSize();
+ doc &&
+ setupMoveUpEvents(
+ this,
+ e,
+ action((moveEv, down: number[], delta: number[]) => {
+ const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
+ doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
+ doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction
+ );
+ };
+ popup = () => {
+ if (!this.renderBounds) return <div />;
+ const { renderBounds } = this;
+ const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+ const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+ const miniSize = this.returnMiniSize();
+ return (
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
+ <CollectionFreeFormView
+ Document={this._props.document}
+ docViewPath={returnEmptyDocViewList}
+ 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 // don't render overlay Docs since they won't scale
+ isContentActive={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ select={emptyFunction}
+ isSelected={returnFalse}
+ dontRegisterView
+ fieldKey={Doc.LayoutFieldKey(this._props.document)}
+ 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}
+ // eslint-disable-next-line no-use-before-define
+ pinToPres={TabDocView.PinDoc}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ fitContentsToBox={returnTrue}
+ xPadding={this.xPadding}
+ yPadding={this.yPadding}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown}>
+ <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
+ </div>
+ </div>
+ );
+ };
+ render() {
+ return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ <div className="miniMap-hidden">
+ <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
+ </div>
+ );
+ }
+}
+
interface TabDocViewProps {
documentId: FieldId;
keyValue?: boolean;
@@ -90,7 +232,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@action
init = (tab: any, doc: Opt<Doc>) => {
if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true;
- if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') {
+ if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') {
tab._disposers = {} as { [name: string]: IReactionDisposer };
tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
tab.DashDoc = doc;
@@ -105,7 +247,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
while (child?.children.length) {
const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
- if (next?.className?.toString().includes(DashFieldView.name)) break;
if (next) child = next;
else break;
}
@@ -129,17 +270,17 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
setupMoveUpEvents(
this,
e,
- e =>
- !e.defaultPrevented &&
- DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
+ moveEv =>
+ !moveEv.defaultPrevented &&
+ DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), moveEv.clientX, moveEv.clientY, undefined, () => {
CollectionDockingView.CloseSplit(doc);
}),
returnFalse,
- action(e => {
+ action(clickEv => {
if (this.view) {
SelectionManager.SelectView(this.view, false);
const child = getChild();
- simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+ simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30);
} else {
this._activated = true;
setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
@@ -149,17 +290,17 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
};
const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
- const closeIcon = <FontAwesomeIcon icon={'eye'} />;
+ const closeIcon = <FontAwesomeIcon icon="eye" />;
ReactDOM.createRoot(iconWrap).render(docIcon);
ReactDOM.createRoot(closeWrap).render(closeIcon);
tab.reactComponents = [iconWrap, closeWrap];
tab.element[0].prepend(iconWrap);
tab._disposers.color = reaction(
- () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
+ () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
({ variant, degree, highlight }) => {
const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant;
- const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color);
+ const textColor = color === variant ? SnappingManager.userColor ?? '' : lightOrDark(color);
titleEle.style.color = textColor;
iconWrap.style.color = textColor;
closeWrap.style.color = textColor;
@@ -185,19 +326,19 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
);
}
// shifts the focus to this tab when another tab is dragged over it
- tab.element[0].onmouseenter = (e: MouseEvent) => {
+ tab.element[0].onmouseenter = () => {
if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
tab.header.parent.setActiveContentItem(tab.contentItem);
tab.setActive(true);
}
this._document && Doc.BrushDoc(this._document);
};
- tab.element[0].onmouseleave = (e: MouseEvent) => {
+ tab.element[0].onmouseleave = () => {
this._document && Doc.UnBrushDoc(this._document);
};
tab.element[0].oncontextmenu = (e: MouseEvent) => {
- let child = getChild();
+ const child = getChild();
if (child) {
simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
e.stopPropagation();
@@ -219,12 +360,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
() => SelectionManager.IsSelected(this._document),
action(selected => {
if (selected) this._activated = true;
- const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
}
- //toggle.style.fontWeight = selected ? 'bold' : '';
- // toggle.style.textTransform = selected ? "uppercase" : "";
}),
{ fireImmediately: true }
);
@@ -232,14 +370,16 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
// highlight the tab when the tab document is brushed in any part of the UI
tab._disposers.reactionDisposer = reaction(
() => doc?.title,
- title => (titleEle.value = title),
+ title => {
+ titleEle.value = title;
+ },
{ fireImmediately: true }
);
// clean up the tab when it is closed
tab.closeElement
- .off('click') //unbind the current click handler
- .click(function () {
+ .off('click') // unbind the current click handler
+ .click(() => {
Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
SelectionManager.DeselectAll();
UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
@@ -249,7 +389,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
/**
* Adds a document to the presentation view
- **/
+ * */
@action
public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
const docList = docs instanceof Doc ? [docs] : docs;
@@ -309,7 +449,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
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
+ pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array
});
if (
// open the presentation trail if it's not already opened
@@ -327,6 +467,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
componentDidMount() {
new _global.ResizeObserver(
action((entries: any) => {
+ // eslint-disable-next-line no-restricted-syntax
for (const entry of entries) {
this._panelWidth = entry.contentRect.width;
this._panelHeight = entry.contentRect.height;
@@ -373,29 +514,30 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
// "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
// "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
// 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) => {
+ addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => {
+ const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
SelectionManager.DeselectAll();
const whereFields = location.split(':');
const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
- if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
+ if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
// prettier-ignore
switch (whereFields[0]) {
case undefined:
case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) {
- const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
+ const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined;
const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
if (lightboxView && (!data || data instanceof List)) {
- lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]);
+ lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs);
return true;
}
}
- return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
- case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
- case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue);
- case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
- case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
+ return LightboxView.Instance.AddDocTab(docs[0], OpenWhere.lightbox);
+ case OpenWhere.close: return CollectionDockingView.CloseSplit(docs[0], whereMods);
+ case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
+ case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
+ case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
}
};
remDocTab = (doc: Doc | Doc[]) => {
@@ -407,16 +549,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
return false;
};
- getCurrentFrame = () => {
- return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
- };
+ getCurrentFrame = () => 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 && !tab.contentItem.config.props.keyValue);
+ const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.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;
};
@action
- focusFunc = (doc: Doc, options: FocusViewOptions) => {
+ focusFunc = () => {
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
}
@@ -426,7 +566,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@observable _forceInvalidateScreenToLocal = 0;
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
- const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity();
};
PanelWidth = () => this._panelWidth;
@@ -434,7 +574,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
miniMapColor = () => Colors.MEDIUM_GRAY;
tabView = () => this._view;
disableMinimap = () => !this._document;
- whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
+ whenChildContentActiveChanges = (isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ };
isContentActive = () => this._isContentActive;
waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
@computed get docView() {
@@ -465,9 +607,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
addDocument={undefined}
removeDocument={this.remDocTab}
addDocTab={this.addDocTab}
- suppressSetHeight={this._document._layout_fitWidth ? true : false}
+ suppressSetHeight={!!this._document._layout_fitWidth}
ScreenToLocalTransform={this.ScreenToLocalTransform}
- dontCenter={'y'}
+ dontCenter="y"
whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
focus={this.focusFunc}
containerViewPath={returnEmptyDoclist}
@@ -485,19 +627,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
style={{
fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
}}
- onPointerOver={action(() => (this._hovering = true))}
- onPointerLeave={action(() => (this._hovering = false))}
- onDragOver={action(() => (this._hovering = true))}
- onDragLeave={action(() => (this._hovering = false))}
+ onPointerOver={action(() => { this._hovering = true; })} // prettier-ignore
+ onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
+ onDragOver={action(() => { this._hovering = true; })} // prettier-ignore
+ onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore
ref={ref => {
- if ((this._mainCont = ref)) {
+ this._mainCont = ref;
+ if (this._mainCont) {
if (this._lastTab) {
this._view && DocumentManager.Instance.RemoveView(this._view);
}
this._lastTab = this.tab;
(this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
- DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
- new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
+ DocServer.GetRefField(this._props.documentId).then(
+ action(doc => {
+ doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+ })
+ );
+ new _global.ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
}
}}>
{this.docView}
@@ -505,142 +652,3 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
);
}
}
-
-interface TabMinimapViewProps {
- document: Doc;
- tabView: () => DocumentView | undefined;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
- PanelWidth: () => number;
- PanelHeight: () => number;
- background: () => string;
-}
-interface TabMiniThumbProps {
- miniWidth: () => number;
- miniHeight: () => number;
- miniTop: () => number;
- miniLeft: () => number;
-}
-
-@observer
-class TabMiniThumb extends React.Component<TabMiniThumbProps> {
- render() {
- return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
- }
-}
-@observer
-export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
- static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
- if (doc) {
- switch (property.split(':')[0]) {
- default:
- return DefaultStyleProvider(doc, props, property);
- case StyleProp.PointerEvents:
- return 'none';
- case StyleProp.DocContents:
- const background = ((type: DocumentType) => {
- // prettier-ignore
- switch (type) {
- case DocumentType.PDF: return 'pink';
- case DocumentType.AUDIO: return 'lightgreen';
- case DocumentType.WEB: return 'brown';
- case DocumentType.IMG: return 'blue';
- case DocumentType.MAP: return 'orange';
- case DocumentType.VID: return 'purple';
- case DocumentType.RTF: return 'yellow';
- case DocumentType.COL: return undefined;
- default: return 'gray';
- }
- })(doc.type as DocumentType);
- return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
- }
- }
- };
-
- @computed get renderBounds() {
- const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
- const bounds = compView?.freeformData?.(true)?.bounds;
- if (!bounds) return undefined;
- const xbounds = bounds.r - bounds.x;
- const ybounds = bounds.b - bounds.y;
- const dim = Math.max(xbounds, ybounds);
- return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
- }
- @computed get xPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
- }
- @computed get yPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
- }
- childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
- miniDown = (e: React.PointerEvent) => {
- const doc = this._props.document;
- const miniSize = this.returnMiniSize();
- doc &&
- setupMoveUpEvents(
- this,
- e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
- const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
- doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
- doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
- return false;
- }),
- emptyFunction,
- emptyFunction
- );
- };
- popup = () => {
- if (!this.renderBounds) return <></>;
- const renderBounds = this.renderBounds;
- const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
- const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
- const miniSize = this.returnMiniSize();
- return (
- <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
- <CollectionFreeFormView
- Document={this._props.document}
- docViewPath={returnEmptyDocViewList}
- 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
- isContentActive={emptyFunction}
- isAnyChildContentActive={returnFalse}
- select={emptyFunction}
- isSelected={returnFalse}
- dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this._props.document)}
- 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}
- xPadding={this.xPadding}
- yPadding={this.yPadding}
- />
- <div className="miniOverlay" onPointerDown={this.miniDown}>
- <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
- </div>
- </div>
- );
- };
- render() {
- return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
- <div className="miniMap-hidden">
- <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4fd49f8fe..fab8e3892 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,11 +1,15 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton, Size } from 'browndash-components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -17,7 +21,8 @@ import { TraceMobx } from '../../../fields/util';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocUtils, Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { LinkManager } from '../../util/LinkManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -35,10 +40,12 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import { TreeSort } from './TreeSort';
import './TreeView.scss';
+
const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export interface TreeViewProps {
treeView: CollectionTreeView;
+ // eslint-disable-next-line no-use-before-define
parentTreeView: TreeView | CollectionTreeView | undefined;
observeHeight: (ref: any) => void;
unobserveHeight: (ref: any) => void;
@@ -48,7 +55,7 @@ export interface TreeViewProps {
treeViewParent: Doc;
renderDepth: number;
dragAction: dropActionType;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
panelWidth: () => number;
panelHeight: () => number;
addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
@@ -87,6 +94,7 @@ const treeBulletWidth = function () {
*/
@observer
export class TreeView extends ObservableReactComponent<TreeViewProps> {
+ // eslint-disable-next-line no-use-before-define
static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
static _openTitleScript: Opt<ScriptField | undefined>;
static _openLevelScript: Opt<ScriptField | undefined>;
@@ -101,6 +109,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
get treeViewOpenIsTransient() {
return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
}
+ @computed get treeViewOpen() {
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+ }
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
@@ -137,9 +148,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
@computed get Document() {
return this._props.Document;
}
- @computed get treeViewOpen() {
- return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
- }
@computed get treeViewExpandedView() {
return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
}
@@ -192,11 +200,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}
return false;
};
- @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+ @undoBatch remove = (docs: Doc | Doc[], key: string) => {
this.treeView._props.select(false);
- const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
+ const ind = DocListCast(this.dataDoc[key]).indexOf(docs instanceof Doc ? docs : docs.lastElement());
- const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+ const res = (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
return res;
};
@@ -221,7 +229,7 @@ export class TreeView extends ObservableReactComponent<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.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+ const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail() && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
}
};
@@ -230,7 +238,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
recurToggle = (childList: Doc[]) => {
if (childList.length > 0) {
childList.forEach(child => {
- child.runProcess = !!!child.runProcess;
+ child.runProcess = !child.runProcess;
TreeView.ToggleChildrenRun.get(child)?.();
});
}
@@ -273,9 +281,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this.recurToggle(this.childDocs);
});
- TreeView.GetRunningChildren.set(this.Document, () => {
- return this.getRunningChildren(this.childDocs);
- });
+ TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs));
}
_treeEle: any;
@@ -301,7 +307,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
super.componentDidUpdate(prevProps);
this._disposers.opening = reaction(
() => this.treeViewOpen,
- open => !open && (this._renderCount = 20)
+ open => {
+ !open && (this._renderCount = 20);
+ }
);
this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
@@ -310,7 +318,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
- onDragUp = (e: PointerEvent) => {
+ onDragUp = () => {
document.removeEventListener('pointerup', this.onDragUp, true);
document.removeEventListener('pointermove', this.onDragMove, true);
};
@@ -324,7 +332,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
document.addEventListener('pointerup', this.onDragUp, true);
}
};
- onPointerLeave = (e: React.PointerEvent): void => {
+ onPointerLeave = (): void => {
Doc.UnBrushDoc(this.dataDoc);
if (this._header.current?.className !== 'treeView-header-editing') {
this._header.current!.className = 'treeView-header';
@@ -385,7 +393,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return this.localAdd(folder);
};
- preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
+ preTreeDrop = () => {
// fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own
};
@@ -395,7 +403,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
if (!this._header.current) return false;
const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.Document;
@@ -403,7 +411,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
e.stopPropagation();
return true;
}
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData && pt[0] < rect.left + rect.width) {
if (docDragData.draggedDocuments[0] === this.Document) return true;
const added = this.dropDocuments(
@@ -423,14 +431,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return false;
};
- localAdd = (doc: Doc | Doc[]) => {
- const innerAdd = (doc: Doc) => {
+ localAdd = (docs: Doc | Doc[]): boolean => {
+ const innerAdd = (doc: Doc): boolean => {
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.SetContainer(doc, DocCast(this.Document.embedContainer));
return added;
};
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ return (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
dropping: boolean = false;
@@ -462,8 +470,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
refTransform = (ref: HTMLDivElement | undefined | null) => {
if (!ref) return this.ScreenToLocalTransform();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
- const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(ref);
+ const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle());
const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
return this.ScreenToLocalTransform().translate(offset[0], offset[1]);
};
@@ -490,26 +498,31 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const ids: { [key: string]: string } = {};
const rows: JSX.Element[] = [];
const doc = this.Document;
- doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ doc &&
+ Object.keys(doc).forEach(key => {
+ !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key);
+ });
+ // eslint-disable-next-line no-restricted-syntax
for (const key of Object.keys(ids).slice().sort()) {
+ // eslint-disable-next-line no-continue
if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- let leftOffset = observable({ width: 0 });
+ const leftOffset = observable({ width: 0 });
const expandedWidth = () => this._props.panelWidth() - leftOffset.width;
if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
- const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
- const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
- const innerAdd = (doc: Doc) => {
+ const remDoc = (docs: Doc | Doc[]) => this.remove(docs, key);
+ const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc);
+ const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
+ const innerAdd = (iDoc: 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.SetContainer(doc, DocCast(this.Document.embedContainer));
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true);
+ dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer));
return added;
};
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ return (docs instanceof Doc ? [docs] : docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean);
};
contentElement = TreeView.GetChildElements(
contents instanceof Doc ? [contents] : DocListCast(contents),
@@ -549,7 +562,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
contentElement = (
<EditableView
key="editableView"
- contents={contents !== undefined ? Field.toString(contents as Field) : 'null'}
+ contents={contents !== undefined ? Field.toString(contents as FieldType) : 'null'}
height={13}
fontSize={12}
GetValue={() => Field.toKeyValueString(doc, key)}
@@ -572,15 +585,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
);
}
rows.push(
- <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
+ <div style={{ display: 'flex', overflow: 'auto' }} key="newKeyValue">
<EditableView
key="editableView"
- contents={'+key=value'}
+ contents="+key=value"
height={13}
fontSize={12}
GetValue={returnEmptyString}
SetValue={input => {
- const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/);
+ const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@?+\-*/ 0-9()]+)/);
if (match) {
const key = match[1];
const assign = match[2];
@@ -620,7 +633,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
docs.push(doc);
- docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
+ docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => {
+ d.zIndex = i;
+ });
}
const 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);
@@ -628,10 +643,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return added;
};
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
+ const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
- let downX = 0,
- downY = 0;
+ let downX = 0;
+ let downY = 0;
if (docs?.length && this._renderCount < docs?.length) {
this._renderTimer && clearTimeout(this._renderTimer);
this._renderTimer = setTimeout(
@@ -667,7 +682,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
- className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
+ className="" // this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
downY = e.clientY;
@@ -719,7 +734,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
</ul>
</div>
);
- } else if (this.treeViewExpandedView === 'fields') {
+ }
+ if (this.treeViewExpandedView === 'fields') {
return (
<ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
<div>{this.expandedField}</div>
@@ -891,14 +907,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
- const treeView = this.treeView;
+ const { treeView } = this;
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
- case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+ case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined; // StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
+ break;
case StyleProp.BoxShadow: return undefined;
- case StyleProp.DocContents:
+ case StyleProp.DocContents: {
const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
return treeView.outlineMode ? null : (
@@ -917,6 +934,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
{StrCast(doc?.title)}
</div>
);
+ }
+ default:
}
return treeView._props.styleProvider?.(doc, props, property);
};
@@ -924,7 +943,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
if (property.startsWith(StyleProp.Decorations)) return null;
return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
- onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ onKeyDown = (e: React.KeyboardEvent) => {
if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
@@ -944,6 +963,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
e.stopPropagation?.();
e.preventDefault?.();
return UndoManager.RunInBatch(this.makeTextCollection, 'bullet');
+ default:
}
}
return false;
@@ -959,22 +979,24 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const view = this._editTitle ? (
<EditableView
key="_editTitle"
- oneLine={true}
- display={'inline-block'}
+ oneLine
+ display="inline-block"
editing={this._editTitle}
- background={'#7089bb'}
+ background="#7089bb"
contents={StrCast(this.Document.title)}
height={12}
- sizeToContent={true}
+ sizeToContent
fontSize={12}
- isEditingCallback={action(e => (this._editTitle = e))}
+ isEditingCallback={action(e => {
+ this._editTitle = e;
+ })}
GetValue={() => StrCast(this.Document.title)}
OnTab={undoBatch((shift?: boolean) => {
if (!shift) this._props.indentDocument?.(true);
else this._props.outdentDocument?.(true);
})}
OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
- OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
+ OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()}
SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
Doc.SetInPlace(this.Document, 'title', value, false);
this.treeView.outlineMode && enterKey && this.makeTextCollection();
@@ -984,7 +1006,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
<DocumentView
key="title"
ref={action((r: any) => {
- this._docRef = r ? r : undefined;
+ this._docRef = r || undefined;
if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
this._docRef.select(false);
this.setEditTitle(this._docRef);
@@ -994,8 +1016,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
Document={this.Document}
layout_fitWidth={returnTrue}
scriptContext={this}
- hideDecorations={true}
- hideClickBehaviors={true}
+ hideDecorations
+ hideClickBehaviors
styleProvider={this.titleStyleProvider}
onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
containerViewPath={this.treeView.childContainerViewPath}
@@ -1015,7 +1037,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
PanelHeight={return18}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- isContentActive={emptyFunction} //this._props.isContentActive}
+ isContentActive={emptyFunction} // this._props.isContentActive}
isDocumentActive={this._props.isContentActive}
focus={this.refocus}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
@@ -1045,99 +1067,101 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}}>
{view}
</div>
- <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
+ <div
+ className="treeView-rightButtons"
+ ref={action((r: any) => {
+ r && (this.headerEleWidth = r.getBoundingClientRect().width);
+ })}>
{this.titleButtons}
</div>
</>
);
}
- renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
- return (
- <>
+ renderBulletHeader = (contents: JSX.Element, editing: boolean) => (
+ <>
+ <div
+ className={`treeView-header` + (editing ? '-editing' : '')}
+ key="titleheader"
+ ref={this._header}
+ onClick={this.ignoreEvent}
+ onPointerDown={e => {
+ this.treeView.isContentActive() &&
+ setupMoveUpEvents(
+ this,
+ e,
+ () => {
+ (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
+ return true;
+ },
+ returnFalse,
+ emptyFunction
+ );
+ }}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}>
<div
- className={`treeView-header` + (editing ? '-editing' : '')}
- key="titleheader"
- ref={this._header}
- onClick={this.ignoreEvent}
- onPointerDown={e => {
- this.treeView.isContentActive() &&
- setupMoveUpEvents(
- this,
- e,
- () => {
- (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
- return true;
- },
- returnFalse,
- emptyFunction
- );
+ className="treeView-background"
+ style={{
+ background: SettingsManager.userColor,
}}
- onPointerEnter={this.onPointerEnter}
- onPointerLeave={this.onPointerLeave}>
- <div
- className="treeView-background"
- style={{
- background: SettingsManager.userColor,
- }}
- />
- {contents}
- </div>
- {this.renderBorder}
- </>
- );
- };
-
- fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
- renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
- return (
- <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
- <DocumentView
- key={this.Document[Id]}
- ref={action((r: DocumentView | null) => (this._dref = r))}
- Document={this.Document}
- layout_fitWidth={this.fitWidthFilter}
- PanelWidth={this.embeddedPanelWidth}
- PanelHeight={this.embeddedPanelHeight}
- LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
- LayoutTemplate={this.treeView._props.childLayoutTemplate}
- isContentActive={isActive}
- isDocumentActive={isActive}
- styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
- fitContentsToBox={returnTrue}
- hideTitle={asText}
- hideDecorations={true}
- hideClickBehaviors={true}
- hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
- ScreenToLocalTransform={this.docTransform}
- renderDepth={this._props.renderDepth + 1}
- onClickScript={this.onChildClick}
- onKey={this.onKeyDown}
- containerViewPath={this.treeView.childContainerViewPath}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- focus={this.refocus}
- addDocument={this._props.addDocument}
- moveDocument={this.move}
- removeDocument={this._props.removeDoc}
- whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
- yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
- addDocTab={this._props.addDocTab}
- pinToPres={this.treeView._props.pinToPres}
- disableBrushing={this.treeView._props.disableBrushing}
- scriptContext={this}
/>
+ {contents}
</div>
- );
- };
+ {this.renderBorder}
+ </>
+ );
+
+ fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
+ renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => (
+ <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
+ <DocumentView
+ key={this.Document[Id]}
+ ref={action((r: DocumentView | null) => {
+ this._dref = r;
+ })}
+ Document={this.Document}
+ layout_fitWidth={this.fitWidthFilter}
+ PanelWidth={this.embeddedPanelWidth}
+ PanelHeight={this.embeddedPanelHeight}
+ LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+ LayoutTemplate={this.treeView._props.childLayoutTemplate}
+ isContentActive={isActive}
+ isDocumentActive={isActive}
+ styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+ fitContentsToBox={returnTrue}
+ hideTitle={asText}
+ hideDecorations
+ hideClickBehaviors
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+ ScreenToLocalTransform={this.docTransform}
+ renderDepth={this._props.renderDepth + 1}
+ onClickScript={this.onChildClick}
+ onKey={this.onKeyDown}
+ containerViewPath={this.treeView.childContainerViewPath}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ focus={this.refocus}
+ addDocument={this._props.addDocument}
+ moveDocument={this.move}
+ removeDocument={this._props.removeDoc}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+ yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this.treeView._props.pinToPres}
+ disableBrushing={this.treeView._props.disableBrushing}
+ scriptContext={this}
+ />
+ </div>
+ );
// 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.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
- <div></div>
+ <div />
) : (
<>
{this.renderBullet}
@@ -1147,14 +1171,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}
// renders the document in the header field instead of a text proxy.
- renderDocumentAsHeader = (asText: boolean) => {
- return (
- <>
- {this.renderBullet}
- {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
- </>
- );
- };
+ renderDocumentAsHeader = (asText: boolean) => (
+ <>
+ {this.renderBullet}
+ {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
+ </>
+ );
@computed get renderBorder() {
const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
@@ -1170,7 +1192,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const pt = [de.clientX, de.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
};
@@ -1185,7 +1207,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`}
ref={this.createTreeDropTarget}
onDrop={this.onTreeDrop}
- //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+ // onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
// onKeyDown={this.onKeyDown}
>
<li className="collection-child">
@@ -1209,11 +1231,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const aN = parseInt(a.match(reN)![0], 10);
const bN = parseInt(b.match(reN)![0], 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
- } else {
- return aA > bA ? 1 : -1;
}
+ return aA > bA ? 1 : -1;
};
- docs.sort(function (d1, d2): 0 | 1 | -1 {
+ docs.sort((d1, d2): 0 | 1 | -1 => {
const a = criterion === TreeSort.AlphaUp ? d2 : d1;
const b = criterion === TreeSort.AlphaUp ? d1 : d2;
const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
@@ -1230,7 +1251,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
childDocs: Doc[],
treeView: CollectionTreeView,
parentTreeView: CollectionTreeView | TreeView | undefined,
- treeView_Parent: Doc,
+ treeViewParent: Doc,
dataDoc: Doc | undefined,
parentCollectionDoc: Doc | undefined,
containerPrevSibling: Doc | undefined,
@@ -1238,13 +1259,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
remove: undefined | ((doc: Doc | Doc[]) => boolean),
move: DragManager.MoveFunction,
dragAction: dropActionType,
- addDocTab: (doc: Doc, where: OpenWhere) => boolean,
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean,
styleProvider: undefined | StyleProviderFuncType,
screenToLocalXf: () => Transform,
isContentActive: (outsideReaction?: boolean) => boolean,
panelWidth: () => number,
renderDepth: number,
- treeView_HideHeaderFields: () => boolean,
+ treeViewHideHeaderFields: () => boolean,
renderedIds: string[],
onCheckedClick: undefined | (() => ScriptField),
onChildClick: undefined | (() => ScriptField),
@@ -1261,19 +1282,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
hierarchyIndex?: number[],
renderCount?: number
) {
- 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(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
+ const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded));
const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
- const treeView_Refs = new Map<Doc, TreeView | undefined>();
+ const treeViewRefs = 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(treeView_Parent, dataDoc, child);
+ const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return null;
}
@@ -1290,7 +1306,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
Doc.SetContainer(child, treeView.Document);
}
};
- const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
+ const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.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);
@@ -1301,12 +1317,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return (
<TreeView
key={child[Id]}
- ref={r => treeView_Refs.set(child, r ? r : undefined)}
+ ref={r => treeViewRefs.set(child, r || undefined)}
Document={pair.layout}
dataDoc={pair.data}
- treeViewParent={treeView_Parent}
+ treeViewParent={treeViewParent}
prevSibling={docs[i]}
- // TODO: [AL] add these
hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
AddToMap={AddToMap}
RemFromMap={RemFromMap}
@@ -1316,7 +1331,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
+ removeDoc={StrCast(treeViewParent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
addDocument={addDocument}
styleProvider={styleProvider}
panelWidth={rowWidth}
@@ -1327,7 +1342,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
addDocTab={addDocTab}
ScreenToLocalTransform={screenToLocalXf}
isContentActive={isContentActive}
- treeViewHideHeaderFields={treeView_HideHeaderFields}
+ treeViewHideHeaderFields={treeViewHideHeaderFields}
renderedIds={renderedIds}
skipFields={skipFields}
firstLevel={firstLevel}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 0acc99360..d2ce17f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -10,6 +10,7 @@ export interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
color: () => string;
+ // eslint-disable-next-line react/require-default-props
isAnnotationOverlay?: boolean;
nativeDimScaling: () => number;
zoomScaling: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 73dd7fea3..fc39cafaa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,13 +5,13 @@ import * as React from 'react';
import { SettingsManager } from '../../../util/SettingsManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
-import { Doc } from '../../../../fields/Doc';
/**
* An Fsa Arc. The first array element is a test condition function that will be observed.
* The second array element is a function that will be invoked when the first test function
* returns a truthy value
*/
+// eslint-disable-next-line no-use-before-define
export type infoArc = [() => any, (res?: any) => infoState];
export const StateMessage = Symbol('StateMessage');
@@ -46,6 +46,7 @@ export function InfoState(
gif?: string,
entryFunc?: () => any
) {
+ // eslint-disable-next-line new-cap
return new infoState(msg, arcs, gif, entryFunc);
}
@@ -73,14 +74,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
}
clearState = () => this._disposers.map(disposer => disposer());
- initState = () => (this._disposers =
- this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
- arc => reaction(
- arc.test,
- res => res && this._props.next(arc.act(res)),
- { fireImmediately: true }
- )
- )); // prettier-ignore
+ initState = () => {
+ this._disposers = this.Arcs
+ .map(arc => ({ test: arc[0], act: arc[1] }))
+ .map(arc => reaction(
+ arc.test,
+ res => res && this._props.next(arc.act(res)),
+ { fireImmediately: true }
+ )
+ )}; // prettier-ignore
componentDidMount() {
this.initState();
@@ -97,10 +99,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
render() {
const gif = this.State?.[StateMessageGIF];
return (
- <div className={'collectionFreeform-infoUI'}>
+ <div className="collectionFreeform-infoUI">
<p className="collectionFreeform-infoUI-msg">
{this.State?.[StateMessage]}
- <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+ <button
+ type="button"
+ className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')}
+ onClick={action(() => {
+ this._expanded = !this._expanded;
+ })}>
{this._expanded ? 'Less...' : 'More...'}
</button>
</p>
@@ -108,7 +115,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
<img src={`/assets/${gif}`} alt="state message gif" />
</div>
<div className="collectionFreeform-infoUI-close">
- <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
+ <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} />
</div>
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index cc729decc..65a529d62 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,22 +1,22 @@
import { makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { StrCast } from '../../../../fields/Types';
-import { DocumentManager } from '../../../util/DocumentManager';
import { LinkManager } from '../../../util/LinkManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { TopBar } from '../../topbar/TopBar';
import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
import './CollectionFreeFormView.scss';
+import { DocData } from '../../../../fields/DocSymbols';
export interface CollectionFreeFormInfoUIProps {
Document: Doc;
- Freeform: CollectionFreeFormView;
- close: () => boolean;
+ LayoutDoc: Doc;
+ childDocs: () => Doc[];
+ close: () => void;
}
@observer
@@ -32,10 +32,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
@observable _currState: infoState | undefined = undefined;
get currState() { return this._currState; } // prettier-ignore
- set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
+ set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore
componentWillUnmount(): void {
- this._props.Freeform.dataDoc.backgroundColor = this._originalbackground;
+ this._props.Document[DocData].backgroundColor = this._originalbackground;
}
setCurrState = (state: infoState) => {
@@ -46,16 +46,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
};
setupStates = () => {
- this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor);
+ this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor);
// state entry functions
- const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour);
- const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
+ // const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore
+ // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
// arc transition trigger conditions
- const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
- const numDocs = () => this._props.Freeform.childDocs.length;
+ const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
+ const numDocs = () => this._props.childDocs().length;
- let docX: FieldResult<Field>;
- let docY: FieldResult<Field>;
+ let docX: FieldResult<FieldType>;
+ let docY: FieldResult<FieldType>;
const docNewX = () => firstDoc()?.x;
const docNewY = () => firstDoc()?.y;
@@ -72,7 +72,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
let trail: number;
- const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails);
const presentationMode = () => Doc.ActivePresentation?.presentation_status;
// set of states
@@ -82,6 +81,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
docCreated: [() => numDocs(), () => {
docX = firstDoc()?.x;
docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
return oneDoc;
}],
}
@@ -92,18 +92,20 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
{
// docCreated: [() => numDocs() > 1, () => multipleDocs],
docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => {
docX = firstDoc()?.x;
docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
return movedDoc;
}],
}
); // prettier-ignore
const movedDoc = InfoState(
- 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")',
+ 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")',
{
- docCreated: [() => numDocs() == 2, () => multipleDocs],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 2, () => multipleDocs],
docDeleted: [() => numDocs() < 1, () => start],
},
'dash-colon-menu.gif',
@@ -113,6 +115,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const multipleDocs = InfoState(
'Let\'s create a new link. Click the link icon on one of your documents.',
{
+ // eslint-disable-next-line no-use-before-define
linkStarted: [() => linkStart(), () => startedLink],
docRemoved: [() => numDocs() < 2, () => oneDoc],
},
@@ -123,6 +126,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
'Now click the highlighted link icon on your other document.',
{
linkUnstart: [() => linkUnstart(), () => multipleDocs],
+ // eslint-disable-next-line no-use-before-define
linkCreated: [() => numDocLinks(), () => madeLink],
docRemoved: [() => numDocs() < 2, () => oneDoc],
},
@@ -135,6 +139,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
linkCreated: [() => !numDocLinks(), () => multipleDocs],
linkViewed: [() => linkMenuOpen(), () => {
alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+ // eslint-disable-next-line no-use-before-define
return viewedLink;
}],
},
@@ -146,10 +151,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
{
linkDeleted: [() => !numDocLinks(), () => multipleDocs],
docRemoved: [() => numDocs() < 2, () => oneDoc],
- docCreated: [() => numDocs() == 3, () => {
+ docCreated: [() => numDocs() === 3, () => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return presentDocs;
}],
+ // eslint-disable-next-line no-use-before-define
activePen: [() => activeTool() === InkTool.Pen, () => penMode],
},
'documentation.png',
@@ -163,6 +170,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc1;
},
],
@@ -185,11 +193,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc2;
},
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -199,11 +209,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc3;
},
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -218,6 +230,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -235,21 +248,24 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const manualPresentationMode = InfoState("You're in manual presentation mode.", {
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
- docCreated: [() => numDocs() == 4, () => completed],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 4, () => completed],
});
const autoPresentationMode = InfoState("You're in auto presentation mode.", {
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
- docCreated: [() => numDocs() == 4, () => completed],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 4, () => completed],
});
const completed = InfoState(
'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.',
- { docRemoved: [() => numDocs() == 1, () => oneDoc] },
+ { docRemoved: [() => numDocs() === 1, () => oneDoc] },
'documentation.png',
() => TopBar.Instance.FlipDocumentationIcon()
); // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index c83c26509..a4496a417 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,5 @@
-import { Doc, Field, FieldResult } from '../../../../fields/Doc';
+/* eslint-disable no-use-before-define */
+import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
import { Id, ToString } from '../../../../fields/FieldSymbols';
import { ObjectField } from '../../../../fields/ObjectField';
import { RefField } from '../../../../fields/RefField';
@@ -48,9 +49,9 @@ export interface PoolData {
export interface ViewDefResult {
ele: JSX.Element;
bounds?: ViewDefBounds;
- inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden
+ inkMask?: number; // sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden
}
-function toLabel(target: FieldResult<Field>) {
+function toLabel(target: FieldResult<FieldType>) {
if (typeof target === 'number' || Number(target)) {
const truncated = Number(Number(target).toFixed(0));
const precise = Number(Number(target).toFixed(2));
@@ -84,9 +85,9 @@ interface PivotColumn {
filters: string[];
}
-export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
const docMap = new Map<string, PoolData>();
- childPairs.forEach(({ layout, data }, i) => {
+ childPairs.forEach(({ layout, data }) => {
docMap.set(layout[Id], {
x: NumCast(layout.x),
y: NumCast(layout.y),
@@ -97,10 +98,15 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
replica: '',
});
});
+ // eslint-disable-next-line no-use-before-define
return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
}
-export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+function toNumber(val: FieldResult<FieldType>) {
+ return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+}
+
+export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
const docMap = new Map<string, PoolData>();
const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
@@ -128,23 +134,23 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
const docMap = new Map<string, PoolData>();
const fieldKey = 'data';
- const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+ const pivotColumnGroups = new Map<FieldResult<FieldType>, PivotColumn>();
let nonNumbers = 0;
const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
- childPairs.map(pair => {
+ childPairs.forEach(pair => {
const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
const num = toNumber(pair.layout[pivotFieldKey]);
- if (num === undefined || Number.isNaN(num)) {
+ if (num === undefined || isNaN(num)) {
nonNumbers++;
}
- const val = Field.toString(pair.layout[pivotFieldKey] as Field);
+ const val = Field.toString(pair.layout[pivotFieldKey] as FieldType);
if (listValue) {
- listValue.forEach((val, i) => {
- !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
- pivotColumnGroups.get(val)!.docs.push(pair.layout);
- pivotColumnGroups.get(val)!.replicas.push(i.toString());
+ listValue.forEach((lval, i) => {
+ !pivotColumnGroups.get(lval) && pivotColumnGroups.set(lval, { docs: [], filters: [lval], replicas: [] });
+ pivotColumnGroups.get(lval)!.docs.push(pair.layout);
+ pivotColumnGroups.get(lval)!.replicas.push(i.toString());
});
} else if (val) {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
@@ -184,11 +190,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
const textlen = Array.from(pivotColumnGroups.keys())
.map(c => getTextWidth(toLabel(c), desc))
.reduce((p, c) => Math.max(p, c), 0 as number);
- const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
+ const maxText = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
const colWidth = panelDim[0] / pivotColumnGroups.size;
- const colHeight = panelDim[1] - max_text;
+ const colHeight = panelDim[1] - maxText;
let numCols = 0;
let bestArea = 0;
let pivotAxisWidth = 0;
@@ -212,7 +218,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
let x = 0;
const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
sortedPivotKeys.forEach(key => {
- const val = pivotColumnGroups.get(key)!;
+ const val = pivotColumnGroups.get(key);
let y = 0;
let xCount = 0;
const text = toLabel(key);
@@ -222,11 +228,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
x,
y: pivotAxisWidth,
width: pivotAxisWidth * expander * numCols,
- height: max_text,
+ height: maxText,
fontSize,
payload: val,
});
- val.docs.forEach((doc, i) => {
+ val?.docs.forEach((doc, i) => {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
@@ -262,19 +268,16 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
payload: pivotColumnGroups.get(key)!.filters,
}));
groupNames.push(...dividers);
- return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
-}
-
-function toNumber(val: FieldResult<Field>) {
- return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+ // eslint-disable-next-line no-use-before-define
+ return normalizeResults(panelDim, maxText, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
-export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) {
+export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps?: any */) {
const fieldKey = 'data';
const pivotDateGroups = new Map<number, Doc[]>();
const docMap = new Map<string, PoolData>();
const groupNames: ViewDefBounds[] = [];
- const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
+ const timelineFieldKey = Field.toString(pivotDoc._pivotField as FieldType);
const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']);
const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null);
const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan;
@@ -290,7 +293,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
childPairs.forEach(pair => {
const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
- if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
+ if (!isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
!pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
pivotDateGroups.get(num)!.push(pair.layout);
minTime = Math.min(num, minTime);
@@ -340,6 +343,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
}
+ // eslint-disable-next-line no-use-before-define
layoutDocsAtTime(keyDocs, key);
});
if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
@@ -400,11 +404,11 @@ function normalizeResults(
const height = aggBounds.b - aggBounds.y === 0 ? 1 : aggBounds.b - aggBounds.y;
const wscale = panelDim[0] / width;
let scale = wscale * height > panelDim[1] ? panelDim[1] / height : wscale;
- if (Number.isNaN(scale)) scale = 1;
+ if (isNaN(scale)) scale = 1;
Array.from(docMap.entries())
.filter(ele => ele[1].pair)
- .map(ele => {
+ .forEach(ele => {
const newPosRaw = ele[1];
if (newPosRaw) {
const newPos: PoolData = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 69cbae86f..90977d955 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -4,26 +4,28 @@ import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { PresBox } from '../../nodes/trails/PresBox';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
import './CollectionFreeFormView.scss';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+
export interface CollectionFreeFormPannableContentsProps {
Document: Doc;
viewDefDivClick?: ScriptField;
children?: React.ReactNode | undefined;
- transition?: string;
+ transition: () => string;
isAnnotationOverlay: boolean | undefined;
+ showPresPaths: () => boolean;
transform: () => string;
brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
}
@observer
-export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+export class CollectionFreeFormPannableContents extends ObservableReactComponent<CollectionFreeFormPannableContentsProps> {
constructor(props: CollectionFreeFormPannableContentsProps) {
super(props);
makeObservable(this);
}
@computed get presPaths() {
- return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null;
+ return this._props.showPresPaths() ? PresBox.Instance.pathLines(this._props.Document) : null;
}
// rectangle highlight used when following trail/link to a region of a collection that isn't a document
showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
@@ -42,7 +44,7 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
render() {
return (
<div
- className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+ className={'collectionfreeformview' + (this._props.viewDefDivClick ? '-viewDef' : '-none')}
onScroll={e => {
const target = e.target as any;
if (getComputedStyle(target)?.overflow === 'visible') {
@@ -50,13 +52,13 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
}
}}
style={{
- transform: this.props.transform(),
- transition: this.props.transition,
- width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+ transform: this._props.transform(),
+ transition: this._props.transition(),
+ width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
}}>
{this.props.children}
{this.presPaths}
- {this.showViewport(this.props.brushedView())}
+ {this.showViewport(this._props.brushedView())}
</div>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index fa8218bdd..f64c6715b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -4,26 +4,22 @@ import * as mobxUtils from 'mobx-utils';
import * as React from 'react';
import * as uuid from 'uuid';
import CursorField from '../../../../fields/CursorField';
-import { Doc, FieldResult } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { Cast } from '../../../../fields/Types';
-import { CollectionViewProps } from '../CollectionView';
+import { CollectionViewProps } from '../CollectionSubView';
import './CollectionFreeFormView.scss';
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
@computed protected get cursors(): CursorField[] {
- const doc = this.props.Document;
-
- let cursors: FieldResult<List<CursorField>>;
- const id = Doc.UserDoc()[Id];
- if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) {
+ const { Document } = this.props;
+ const cursors = Cast(Document.cursors, listSpec(CursorField));
+ if (!cursors) {
return [];
}
const now = mobxUtils.now();
- return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000);
+ return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== Document[Id] && now - metadata.timestamp < 1000);
}
@computed get renderedCursors() {
@@ -33,46 +29,44 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
metadata,
position: { x, y },
},
- }) => {
- return (
- <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
- <canvas
- className="collectionFreeFormRemoteCursors-canvas"
- ref={el => {
- if (el) {
- const ctx = el.getContext('2d');
- if (ctx) {
- ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
- ctx.fillRect(0, 0, 20, 20);
+ }) => (
+ <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
+ <canvas
+ className="collectionFreeFormRemoteCursors-canvas"
+ ref={el => {
+ if (el) {
+ const ctx = el.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
+ ctx.fillRect(0, 0, 20, 20);
- ctx.fillStyle = 'black';
- ctx.lineWidth = 0.5;
+ ctx.fillStyle = 'black';
+ ctx.lineWidth = 0.5;
- ctx.beginPath();
+ ctx.beginPath();
- ctx.moveTo(10, 0);
- ctx.lineTo(10, 8);
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
- ctx.moveTo(10, 20);
- ctx.lineTo(10, 12);
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
- ctx.moveTo(0, 10);
- ctx.lineTo(8, 10);
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
- ctx.moveTo(20, 10);
- ctx.lineTo(12, 10);
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
- ctx.stroke();
- }
+ ctx.stroke();
}
- }}
- width={20}
- height={20}
- />
- <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
- </div>
- );
- }
+ }
+ }}
+ width={20}
+ height={20}
+ />
+ <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
+ </div>
+ )
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3fab00968..12a299ce6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,14 +1,18 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc';
import { DocData, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
-import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
+import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
@@ -16,13 +20,14 @@ import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
+import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { CompileScript } from '../../../util/Scripting';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -33,8 +38,8 @@ import { Transform } from '../../../util/Transform';
import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
+import { PinProps } from '../../DocComponent';
import { GestureOverlay } from '../../GestureOverlay';
-import { CtrlKey } from '../../GlobalKeyHandler';
import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
@@ -42,7 +47,8 @@ import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../../nodes/trails/PresBox';
+import { PresBox } from '../../nodes/trails/PresBox';
+// eslint-disable-next-line import/extensions
import { CreateImage } from '../../nodes/WebBoxRenderer';
import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
@@ -71,13 +77,13 @@ export interface collectionFreeformViewProps {
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
public get displayName() {
- return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
+ return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')';
} // this makes mobx trace() statements more descriptive
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
const field = this.dataDoc[this.fieldKey];
- const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
+ const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim();
return !paintFunc
? ''
: paintFunc.includes('dashDiv')
@@ -89,8 +95,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
super(props);
makeObservable(this);
}
- @observable
- public static ShowPresPaths = false;
private _panZoomTransitionTimer: any;
private _lastX: number = 0;
@@ -233,13 +237,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
@observable _keyframeEditing = false;
- @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
+ @action setKeyFrameEditing = (set: boolean) => {
+ this._keyframeEditing = set;
+ };
getKeyFrameEditing = () => this._keyframeEditing;
onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick);
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
+ panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
fitContentOnce = () => {
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -251,7 +258,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -267,7 +274,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if ((retVal = this._props.addDocument?.(newBox) || false)) {
+ retVal = this._props.addDocument?.(newBox) || false;
+ if (retVal) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
@@ -277,15 +285,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
if (retVal) {
const newBoxes = newBox instanceof Doc ? [newBox] : newBox;
- for (const newBox of newBoxes) {
- if (newBox.activeFrame !== undefined) {
- const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]);
- CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]);
- CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]);
- delete newBox.activeFrame;
- CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
+ newBoxes.forEach(box => {
+ if (box.activeFrame !== undefined) {
+ const vals = CollectionFreeFormDocumentView.animFields.map(field => box[field.key]);
+ CollectionFreeFormDocumentView.animFields.forEach(field => delete box[`${field.key}_indexed`]);
+ CollectionFreeFormDocumentView.animFields.forEach(field => delete box[field.key]);
+ delete box.activeFrame;
+ CollectionFreeFormDocumentView.animFields.forEach((field, i) => {
+ field.key !== 'opacity' && (box[field.key] = vals[i]);
+ });
}
- }
+ });
if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
}
@@ -308,14 +318,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
focus = (anchor: Doc, options: FocusViewOptions) => {
- if (this._lightboxDoc) return;
+ if (this._lightboxDoc) return undefined;
if (anchor === this.Document) {
// if (options.willZoomCentered && options.zoomScale) {
// this.fitContentOnce();
// options.didMove = true;
// }
}
- if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return;
+ // prettier-ignore
+ if (anchor.type !== DocumentType.CONFIG &&
+ !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && //
+ !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) {
+ return undefined;
+ }
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -331,12 +346,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
return focusTime;
}
+ return undefined;
};
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
- if (doc === this.Document) return res(this.DocumentView?.());
+ if (doc === this.Document) {
+ res(this.DocumentView?.());
+ return;
+ }
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -352,7 +371,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.map(pair => pair.layout)
.slice()
.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1));
+ zsorted.forEach((doc, index) => {
+ doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1;
+ });
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
@@ -405,7 +426,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
- added = this._props.addDocument?.(source) ? true : false;
+ added = !!this._props.addDocument?.(source);
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
if (de.complete.linkDocument) {
de.complete.linkDocument.layout_isSvg = true;
@@ -420,8 +441,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
- else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
- else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
+ if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
+ if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
return false;
};
@@ -482,8 +503,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@action
- updateClusters(_freeform_useClusters: boolean) {
- this.Document._freeform_useClusters = _freeform_useClusters;
+ updateClusters(useClusters: boolean) {
+ this.Document._freeform_useClusters = useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -493,12 +514,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
if (this.Document._freeform_useClusters) {
const docFirst = docs[0];
- docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
+ docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
const preferredInd = NumCast(docFirst.layout_cluster);
- docs.map(doc => (doc.layout_cluster = -1));
+ docs.forEach(doc => {
+ doc.layout_cluster = -1;
+ });
docs.map(doc =>
this._clusterSets.map((set, i) =>
- set.map(member => {
+ set.forEach(member => {
if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
docFirst.layout_cluster = i;
}
@@ -513,19 +536,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
) {
docFirst.layout_cluster = preferredInd;
}
- this._clusterSets.map((set, i) => {
+ this._clusterSets.forEach((set, i) => {
if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
docFirst.layout_cluster = i;
}
});
if (docFirst.layout_cluster === -1) {
- docs.map(doc => {
+ docs.forEach(doc => {
doc.layout_cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
});
} else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc));
+ docs.forEach(doc => {
+ this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc);
+ });
}
childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
}
@@ -568,17 +593,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (doc && this.childDocList?.includes(doc))
switch (property.split(':')[0]) {
case StyleProp.BackgroundColor:
- const cluster = NumCast(doc?.layout_cluster);
- if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
- if (this._clusterSets.length <= cluster) {
- setTimeout(() => doc && this.updateCluster(doc));
- } else {
- // choose a cluster color from a palette
- const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
- styleProp = colors[cluster % colors.length];
- const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
- // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set?.map(s => (styleProp = StrCast(s.backgroundColor)));
+ {
+ const cluster = NumCast(doc?.layout_cluster);
+ if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
+ if (this._clusterSets.length <= cluster) {
+ setTimeout(() => doc && this.updateCluster(doc));
+ } else {
+ // choose a cluster color from a palette
+ const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+ styleProp = colors[cluster % colors.length];
+ const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
+ // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
+ set?.forEach(s => {
+ styleProp = StrCast(s.backgroundColor);
+ });
+ }
}
}
break;
@@ -586,6 +615,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (doc && this.Document._currentFrame !== undefined) {
return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
}
+ break;
+ default:
}
return styleProp;
};
@@ -621,9 +652,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case InkTool.None:
if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1, false);
}
break;
+ default:
}
}
}
@@ -634,13 +666,34 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
- default:
- case GestureUtils.Gestures.Line:
- case GestureUtils.Gestures.Circle:
- case GestureUtils.Gestures.Rectangle:
- case GestureUtils.Gestures.Triangle:
- case GestureUtils.Gestures.Stroke:
- const points = ge.points;
+ // case Gestures.Rectangle:
+ // {
+ // const strokes = this.getActiveDocuments()
+ // .filter(doc => doc.type === DocumentType.INK)
+ // .map(i => {
+ // const d = Cast(i.stroke, InkField);
+ // const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+ // const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
+ // return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
+ // });
+
+ // CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
+ // }
+ // break;
+ case Gestures.Text:
+ if (ge.text) {
+ const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
+ this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+ e.stopPropagation();
+ }
+ break;
+ case Gestures.Line:
+ case Gestures.Circle:
+ case Gestures.Rectangle:
+ case Gestures.Triangle:
+ case Gestures.Stroke:
+ default: {
+ const { points } = ge;
const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
const inkDoc = Docs.Create.InkDocument(
@@ -659,29 +712,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
this.addDocument(inkDoc);
e.stopPropagation();
- break;
- case GestureUtils.Gestures.Rectangle:
- const strokes = this.getActiveDocuments()
- .filter(doc => doc.type === DocumentType.INK)
- .map(i => {
- const d = Cast(i.stroke, InkField);
- const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
- const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
- return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
- });
-
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
- break;
- case GestureUtils.Gestures.Text:
- if (ge.text) {
- const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
- this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
- e.stopPropagation();
- }
+ }
}
};
@action
- onEraserUp = (e: PointerEvent): void => {
+ onEraserUp = (): void => {
this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document));
this._deleteList = [];
this._batch?.end();
@@ -690,7 +725,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onClick = (e: React.MouseEvent) => {
if (this._lightboxDoc) this._lightboxDoc = undefined;
- if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
+ if (ClientUtils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
if (this.onBrowseClickHandler()) {
this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
e.stopPropagation();
@@ -746,7 +781,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
segments.forEach(segment =>
this.forceStrokeGesture(
e,
- GestureUtils.Gestures.Stroke,
+ Gestures.Stroke,
segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
)
);
@@ -759,7 +794,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
return false;
};
- forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+ forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => {
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
@@ -787,7 +822,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.childDocs
.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
- .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+ .map(inkView => inkView!)
+ .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView }))
.filter(
({ inkViewBounds }) =>
inkViewBounds && // bounding box of eraser segment and ink stroke overlap
@@ -802,7 +838,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// Convert from screen space to ink space for the intersection.
const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
- for (var i = 0; i < inkData.length - 3; i += 4) {
+ for (let i = 0; i < inkData.length - 3; i += 4) {
const rawIntersects = InkField.Segment(inkData, i).intersects({
// compute all unique intersections
p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
@@ -828,16 +864,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
segmentInkStroke = (ink: DocumentView, excludeT: number): Segment[] => {
const segments: Segment[] = [];
- var segment: Segment = [];
- var startSegmentT = 0;
+ let segment: Segment = [];
+ let startSegmentT = 0;
const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData();
// This iterates through all segments of the curve and splits them where they intersect another curve.
// if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted)
- for (var i = 0; i < inkData.length - 3; i += 4) {
+ for (let i = 0; i < inkData.length - 3; i += 4) {
const inkSegment = InkField.Segment(inkData, i);
// Getting all t-value intersections of the current curve with all other curves.
const tVals = this.getInkIntersections(i, ink, inkSegment).sort();
if (tVals.length) {
+ // eslint-disable-next-line no-loop-func
tVals.forEach((t, index) => {
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
@@ -890,9 +927,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
- for (var j = 0; j < otherCtrlPts.length - 3; j += 4) {
+ for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
const neighboringSegment = i === j || i === j - 4 || i === j + 4;
// Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
+ // eslint-disable-next-line no-continue
if (ink?.Document === otherInk.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -903,7 +941,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
tVals.push(apt.t);
}
- this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
+ this.bintersects(curve, otherCurve).forEach((val: string | number /* , i: number */) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
@@ -956,7 +994,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
switch (
!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
- e.ctrlKey && !CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
+ e.ctrlKey && !SnappingManager.CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
) {
case freeformScrollMode.Pan:
@@ -966,8 +1004,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
break;
}
- default:
+ // eslint-disable-next-line no-fallthrough
case freeformScrollMode.Zoom:
+ default:
if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
// e.preventDefault();
@@ -977,7 +1016,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
- setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
+ setPan(panXIn: number, panYIn: number, panTime: number = 0, clamp: boolean = false) {
+ let panX = panXIn;
+ let panY = panYIn;
// this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
@@ -1029,14 +1070,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
(!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
- if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
- const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
- this.layoutDoc.layout_scrollTop = undefined;
- newPanY = minPanY + relTop * (maxPanY - minPanY);
- } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
- const maxPanY = minPanY + fitYscroll;
- const relTop = (panY - minPanY) / (maxPanY - minPanY);
- setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+ if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
+ const maxPanScrollY = minPanY + fitYscroll;
+ const relTop = (panY - minPanY) / (maxPanScrollY - minPanY);
+ setTimeout(() => {
+ this.layoutDoc.layout_scrollTop = relTop * maxScrollTop;
+ }, 10);
newPanY = minPanY;
}
!this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
@@ -1088,7 +1127,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._panZoomTransition = transitionTime;
this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
this._panZoomTransitionTimer = setTimeout(
- action(() => (this._panZoomTransition = 0)),
+ action(() => {
+ this._panZoomTransition = 0;
+ }),
transitionTime
);
};
@@ -1171,6 +1212,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation?.();
return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
}
+ return undefined;
};
@computed get childPointerEvents() {
const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
@@ -1192,6 +1234,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childData = entry.pair.data;
return (
<CollectionFreeFormDocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...OmitKeys(entry, ['replica', 'pair']).omit}
key={childLayout[Id] + (entry.replica || '')}
Document={childLayout}
@@ -1203,7 +1246,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isGroupActive={this._props.isGroupActive}
renderDepth={this._props.renderDepth + 1}
hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
- suppressSetHeight={this.layoutEngine ? true : false}
+ suppressSetHeight={!!this.layoutEngine}
RenderCutoffProvider={this.renderCutoffProvider}
CollectionFreeFormView={this}
LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
@@ -1237,37 +1280,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
/>
);
}
- addDocTab = action((doc: Doc, where: OpenWhere) => {
- if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
+ addDocTab = action((docsIn: Doc | Doc[], where: OpenWhere) => {
+ const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
+ if (this._props.isAnnotationOverlay) return this._props.addDocTab(docs, where);
switch (where) {
case OpenWhere.inParent:
- return this._props.addDocument?.(doc) || false;
- case OpenWhere.inParentFromScreen:
- const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
+ return this._props.addDocument?.(docs) || false;
+ case OpenWhere.inParentFromScreen: {
+ const docContext = DocCast(docs[0]?.embedContainer);
return (
(this.addDocument?.(
- (doc instanceof Doc ? [doc] : doc).map(doc => {
- const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
- doc.x = pt[0];
- doc.y = pt[1];
+ (docs instanceof Doc ? [docs] : docs).map(doc => {
+ [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
return doc;
})
) &&
(!docContext || this._props.removeDocument?.(docContext))) ||
false
);
+ }
case undefined:
case OpenWhere.lightbox:
- if (this.layoutDoc._isLightbox) {
- this._lightboxDoc = doc;
- return true;
- }
- if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
- if (doc.hidden) doc.hidden = false;
- return true;
+ {
+ const firstDoc = docs[0];
+ if (this.layoutDoc._isLightbox) {
+ this._lightboxDoc = firstDoc;
+ return true;
+ }
+ if (firstDoc === this.Document || this.childDocList?.includes(firstDoc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(firstDoc)) {
+ if (firstDoc.hidden) firstDoc.hidden = false;
+ return true;
+ }
}
+ break;
+ default:
}
- return this._props.addDocTab(doc, where);
+ return this._props.addDocTab(docs, where);
});
@observable _lightboxDoc: Opt<Doc> = undefined;
@@ -1289,8 +1337,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
return {
- x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
- y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
+ x: isNaN(NumCast(x)) ? 0 : NumCast(x),
+ y: isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
autoDim,
rotation,
@@ -1312,9 +1360,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
};
- viewDefsToJSX = (views: ViewDefBounds[]) => {
- return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
- };
+ viewDefsToJSX = (views: ViewDefBounds[]) => (!Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!));
viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
const { x, y, z } = viewDef;
@@ -1335,7 +1381,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
),
bounds: viewDef,
};
- } else if (viewDef.type === 'div') {
+ }
+ if (viewDef.type === 'div') {
return [x, y].some(val => val === undefined)
? undefined
: {
@@ -1351,9 +1398,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
bounds: viewDef,
};
}
+ return undefined;
}
renderCutoffProvider = computedFn(
+ // eslint-disable-next-line prefer-arrow-callback
function renderCutoffProvider(this: any, doc: Doc) {
return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
}.bind(this)
@@ -1367,7 +1416,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
return [] as ViewDefResult[];
}
@@ -1383,6 +1432,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
+ default:
}
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
@@ -1424,8 +1474,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return anchor;
};
- @action closeInfo = () => (Doc.IsInfoUIDisabled = true);
- infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+ childDocsFunc = () => this.childDocs;
+ @action closeInfo = () => { Doc.IsInfoUIDisabled = true }; // prettier-ignore
+ infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} LayoutDoc={this.layoutDoc} childDocs={this.childDocsFunc} close={this.closeInfo} />);
componentDidMount() {
this._props.setContentViewBox?.(this);
@@ -1466,7 +1517,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.pointerevents = reaction(
() => this.childPointerEvents,
- pointerevents => (this._childPointerEvents = pointerevents as any),
+ pointerevents => {
+ this._childPointerEvents = pointerevents as any;
+ },
{ fireImmediately: true }
);
@@ -1483,6 +1536,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!code.includes('dashDiv')) {
const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
if (script.compiled) script.run({ this: this.DocumentView?.() });
+ // eslint-disable-next-line no-eval
} else code && !first && eval?.(code);
},
{ fireImmediately: true }
@@ -1491,7 +1545,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.layoutElements = reaction(
// layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters
() => this.doInternalLayoutComputation,
- computation => (this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData)),
+ computation => {
+ this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData);
+ },
{ fireImmediately: true }
);
}
@@ -1512,7 +1568,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
try {
- img.src = canvas.toDataURL(); //image source
+ img.src = canvas.toDataURL(); // image source
} catch (e) {
console.log(e);
}
@@ -1567,14 +1623,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const htmlString = new XMLSerializer().serializeToString(newDiv);
const nativeWidth = width;
const nativeHeight = height;
- return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
- .then(async (data_url: any) => {
- const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
+ return CreateImage(ClientUtils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
+ .then(async (dataUrl: any) => {
+ const returnedFilename = await ClientUtils.convertDataUri(dataUrl, filename, noSuffix, replaceRootFilename);
cb(returnedFilename as string, nativeWidth, nativeHeight);
})
- .catch(function (error: any) {
- console.error('oops, something went wrong!', error);
- });
+ .catch((error: any) => console.error('oops, something went wrong!', error));
}
componentWillUnmount() {
@@ -1583,14 +1637,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@action
- onCursorMove = (e: React.PointerEvent) => {
+ onCursorMove = () => {
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
};
@undoBatch
promoteCollection = () => {
const childDocs = this.childDocs.slice();
- childDocs.forEach(doc => {
+ childDocs.forEach(docIn => {
+ const doc = docIn;
const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = scr?.[0];
doc.y = scr?.[1];
@@ -1604,7 +1659,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const width = Math.max(...docs.map(doc => NumCast(doc._width))) + 20;
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
- docs.forEach((doc, i) => {
+ docs.forEach((docIn, i) => {
+ const doc = docIn;
doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
});
@@ -1637,7 +1693,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
- onContextMenu = (e: React.MouseEvent) => {
+ onContextMenu = () => {
if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
@@ -1648,7 +1704,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
return;
}
- !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+ !Doc.noviceMode &&
+ Doc.UserDoc().defaultTextLayout &&
+ appearanceItems.push({
+ description: 'Reset default note style',
+ event: () => {
+ Doc.UserDoc().defaultTextLayout = undefined;
+ },
+ icon: 'eye',
+ });
appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' });
!Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
@@ -1663,8 +1727,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const optionItems = options && 'subitems' in options ? options.subitems : [];
!this._props.isAnnotationOverlay &&
!Doc.noviceMode &&
- optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
- this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' });
+ optionItems.push({
+ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline',
+ event: action(() => {
+ this._showAnimTimeline = !this._showAnimTimeline;
+ }),
+ icon: 'eye',
+ });
+ this._props.renderDepth &&
+ optionItems.push({
+ description: 'Use Background Color as Default',
+ event: () => {
+ Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+ },
+ icon: 'palette',
+ });
this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
@@ -1704,14 +1781,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
activeDocs
- .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))
+ .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))
.forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
const horizLines: number[] = [];
const vertLines: number[] = [];
const invXf = this.screenToFreeformContentsXf.inverse();
snappableDocs
- .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
+ .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))))
.forEach(doc => {
const { left, top, width, height } = docDims(doc);
const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1727,10 +1804,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
- const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+ const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
- for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
+ for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) {
+ this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true);
}
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
@@ -1744,6 +1821,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
+ showPresPaths = () => SnappingManager.ShowPresPaths;
brushedView = () => this._brushedView;
gridColor = () =>
DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
@@ -1776,7 +1854,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.PanZoomCenterXf}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
+ showPresPaths={this.showPresPaths}
+ transition={this.panZoomTransition}
viewDefDivClick={this._props.viewDefDivClick}>
{this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
{this.contentViews}
@@ -1828,7 +1907,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._brushtimer1 = setTimeout(
action(() => {
this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
- this._brushtimer = setTimeout(action(() => (this._brushedView = undefined)), holdTime); // prettier-ignore
+ this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore
}),
transTime + 1
);
@@ -1900,7 +1979,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
) : (
<>
{this._firstRender ? this.placeholder : this.marqueeView}
- {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+ {
+ // eslint-disable-next-line no-use-before-define
+ this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />
+ }
{!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
</>
)}
@@ -1923,11 +2005,12 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
if (!focused) {
const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
- let containers = dv.containerViewPath?.() ?? [];
+ const containers = dv.containerViewPath?.() ?? [];
let parFfview = dv.CollectionFreeFormView;
- for (var cont of containers) {
+ containers.forEach(cont => {
parFfview = parFfview ?? cont.CollectionFreeFormView;
- }
+ });
+
while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView;
const ffview = selfFfview && selfFfview.layoutDoc[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.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
@@ -1936,17 +2019,22 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
});
}
ScriptingGlobals.add(CollectionBrowseClick);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
!readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
!readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
const selView = SelectionManager.Views;
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
SelectionManager.Views.forEach(view =>
view._props.pinToPres(view.Document, {
@@ -1959,29 +2047,33 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
})
);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function bringToFront() {
SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document));
});
-ScriptingGlobals.add(function sendToBack(doc: Doc) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function sendToBack() {
SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
});
-ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function datavizFromSchema() {
// creating a dataviz doc to represent the schema table
- SelectionManager.Views.forEach(view => {
+ SelectionManager.Views.forEach(viewIn => {
+ const view = viewIn;
if (!view.layoutDoc.schema_columnKeys) {
view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
}
- const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+ const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text');
if (!keys) return;
const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
- let csvRows = [];
+ const csvRows = [];
csvRows.push(keys.join(','));
for (let i = 0; i < children.length; i++) {
- let eachRow = [];
+ const eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]]?.toString();
- if (cell) cell = cell.toString().replace(/\,/g, '');
+ let cell = children[i][keys[j]]?.toString();
+ if (cell) cell = cell.toString().replace(/,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 79cc534dc..adac5a102 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -9,6 +9,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: MarqueeOptionsMenu;
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
@@ -29,14 +30,13 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
}
render() {
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
const buttons = (
<>
- <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={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
+ <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={<FontAwesomeIcon icon="map-pin" />} 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 6eca91e9d..2f9cc49e0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,9 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, intersectRect, lightOrDark, returnFalse } from '../../../../Utils';
+import { ClientUtils, lightOrDark, returnFalse } from '../../../../ClientUtils';
+import { intersectRect } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -20,6 +22,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
+import { MarqueeViewBounds } from '../../DocComponent';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { PreviewCursor } from '../../PreviewCursor';
import { OpenWhere } from '../../nodes/DocumentView';
@@ -28,6 +31,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { SubCollectionViewProps } from '../CollectionSubView';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
+
interface MarqueeViewProps {
getContainerTransform: () => Transform;
getTransform: () => Transform;
@@ -44,13 +48,6 @@ interface MarqueeViewProps {
slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
}
-export interface MarqueeViewBounds {
- left: number;
- top: number;
- width: number;
- height: number;
-}
-
@observer
export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
@@ -102,7 +99,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
@action
onKeyDown = (e: KeyboardEvent) => {
- //make textbox and add it to this collection
+ // make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
@@ -141,7 +138,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
}
let ypos = y;
- ns.map(line => {
+ ns.forEach(line => {
const indent = line.search(/\S|$/);
const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
this._props.addDocument?.(newBox);
@@ -155,7 +152,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
@@ -169,7 +166,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
this._props.addDocument?.(slide);
e.stopPropagation();
- }*/ else if (e.key === 'p' && e.ctrlKey) {
+ } */ else if (e.key === 'p' && e.ctrlKey) {
e.preventDefault();
(async () => {
const text: string = await navigator.clipboard.readText();
@@ -184,7 +181,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
e.stopPropagation();
}
};
- //heuristically converts pasted text into a table.
+ // heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
// skips all rows until it gets to a row with more than one entry
// assumes that 1st row has header entry for each column
@@ -192,15 +189,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
// 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) {
- let csvRows = [];
+ const 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) {
+ eachRow.push(eachCell[i].replace(/,/g, ''));
+ if (i % headers.length === 0) {
csvRows.push(eachRow);
eachRow = [];
}
@@ -233,7 +230,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
this._lastY = e.pageY;
this._lassoPts.push([e.clientX, e.clientY]);
if (!e.cancelBubble) {
- if (!Utils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
+ if (!ClientUtils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
if (!this._commandExecuted) {
this.showMarquee();
}
@@ -320,7 +317,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
@action
onClick = (e: React.MouseEvent): void => {
if (this._props.pointerEvents?.() === 'none') return;
- if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+ if (ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
if (Doc.ActiveTool === InkTool.None) {
if (!this._props.trySelectCluster(e.shiftKey)) {
!SnappingManager.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
@@ -335,15 +332,21 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
@action
- showMarquee = () => (this._visible = true);
+ showMarquee = () => {
+ this._visible = true;
+ };
@action
- hideMarquee = () => (this._visible = false);
+ hideMarquee = () => {
+ this._visible = false;
+ };
@undoBatch
delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc)));
+ selected.forEach(doc => {
+ hide ? (doc.hidden = true) : this._props.removeDocument?.(doc);
+ });
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -374,7 +377,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
});
@undoBatch
- pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pileup = action(() => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
selected.forEach(d => this._props.removeDocument?.(d));
@@ -482,7 +485,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
});
@undoBatch
- summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+ summary = action(() => {
const selected = this.marqueeSelect(false).map(d => {
this._props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
@@ -527,8 +530,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
(e as any).propagationIsStopped = true;
if (e.key === 'g') this.collection(e, true);
if (e.key === 'c' || e.key === 't') this.collection(e);
- if (e.key === 's' || e.key === 'S') this.summary(e);
- if (e.key === 'p') this.pileup(e);
+ if (e.key === 's' || e.key === 'S') this.summary();
+ if (e.key === 'p') this.pileup();
this.cleanupInteractions(false);
}
if (e.key === 'r' || e.key === ' ') {
@@ -540,6 +543,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
touchesLine(r1: { left: number; top: number; width: number; height: number }) {
+ // eslint-disable-next-line no-restricted-syntax
for (const lassoPt of this._lassoPts) {
const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
@@ -560,6 +564,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
let hasLeft = false;
let hasBottom = false;
let hasRight = false;
+ // eslint-disable-next-line no-restricted-syntax
for (const lassoPt of this._lassoPts) {
const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
@@ -662,6 +667,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
render() {
return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className="marqueeView"
ref={r => {
@@ -673,7 +679,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
}}
onDragOver={e => e.preventDefault()}
- onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)}
+ onScroll={e => {
+ e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
+ }}
onClick={this.onClick}
onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index f25872c2b..1634daaf7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,8 @@ import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
@@ -16,6 +17,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionGridView.scss';
import Grid, { Layout } from './Grid';
+
@observer
export class CollectionGridView extends CollectionSubView() {
private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -76,15 +78,13 @@ export class CollectionGridView extends CollectionSubView() {
pairs.forEach((pair, i) => {
const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
if (existing) newLayouts.push(existing);
- else {
- if (Object.keys(this.dropLocation).length) {
- // external drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
- this.dropLocation = {};
- } else {
- // internal drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
- }
+ else if (Object.keys(this.dropLocation).length) {
+ // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ } else {
+ // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
}
});
pairs?.length && this.setLayoutList(newLayouts);
@@ -139,9 +139,7 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Creates a layout object for a grid item
*/
- makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
- return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static };
- };
+ makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
/**
* Adds a layout to the list of layouts.
@@ -189,6 +187,7 @@ export class CollectionGridView extends CollectionSubView() {
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return (
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -218,7 +217,7 @@ export class CollectionGridView extends CollectionSubView() {
if (this.flexGrid) {
const savedLayouts = this.savedLayoutList;
this.childLayoutPairs.forEach(({ layout: doc }) => {
- const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+ const gridLayout = savedLayouts.find(layout => layout.i === doc[Id]);
if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
});
@@ -317,7 +316,9 @@ export class CollectionGridView extends CollectionSubView() {
e,
returnFalse,
action(() => {
- undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))();
+ undoBatch(() => {
+ this.Document.gridRowHeight = this._rowHeight;
+ })();
this._rowHeight = undefined;
}),
emptyFunction,
@@ -331,8 +332,20 @@ export class CollectionGridView extends CollectionSubView() {
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' });
- displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' });
+ displayOptionsMenu.push({
+ description: 'Toggle Content Display Style',
+ event: () => {
+ this.Document.display = this.Document.display ? undefined : 'contents';
+ },
+ icon: 'copy',
+ });
+ displayOptionsMenu.push({
+ description: 'Toggle Vertical Centering',
+ event: () => {
+ this.Document.centerY = !this.Document.centerY;
+ },
+ icon: 'copy',
+ });
ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
};
@@ -346,14 +359,14 @@ export class CollectionGridView extends CollectionSubView() {
e,
returnFalse,
returnFalse,
- (e: PointerEvent, doubleTap?: boolean) => {
- if (doubleTap && !e.button) {
+ (clickEv: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap && !clickEv.button) {
undoBatch(
action(() => {
const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
Doc.AddDocToList(this.Document, this._props.fieldKey, text);
- this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+ this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY))));
})
)();
}
@@ -386,7 +399,7 @@ export class CollectionGridView extends CollectionSubView() {
width={this._props.PanelWidth()}
nodeList={this.contents.length ? this.contents : null}
layout={this.contents.length ? this.renderedLayoutList : undefined}
- childrenDraggable={this._props.isSelected() ? true : false}
+ childrenDraggable={!!this._props.isSelected()}
numCols={this.numCols}
rowHeight={this.rowHeight}
setLayout={this.setLayout}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 228af78aa..e68ed0e17 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { IReactionDisposer, action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnTrue } from '../../../../Utils';
+import { ClientUtils, returnTrue } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -12,7 +15,8 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { UndoStack } from '../../UndoStack';
@@ -46,13 +50,15 @@ export class CollectionLinearView extends CollectionSubView() {
this._dropDisposer?.();
this._widthDisposer?.();
this._selectedDisposer?.();
- this.childLayoutPairs.map((pair, ind) => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+ this.childLayoutPairs.map(pair => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
}
componentDidMount() {
this._widthDisposer = reaction(
() => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
- width => this.childDocs.length && (this.layoutDoc._width = width),
+ width => {
+ this.childDocs.length && (this.layoutDoc._width = width);
+ },
{ fireImmediately: true }
);
}
@@ -64,14 +70,14 @@ export class CollectionLinearView extends CollectionSubView() {
dimension = () => NumCast(this.layoutDoc._height);
getTransform = (ele: Opt<HTMLDivElement>) => {
if (!ele) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(ele);
return new Transform(-translateX, -translateY, 1);
};
@action
exitLongLinks = () => {
if (DocumentLinksButton.StartLink?.Document) {
- action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
+ action(() => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
}
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -97,8 +103,8 @@ export class CollectionLinearView extends CollectionSubView() {
e.preventDefault();
};
- getLinkUI = () => {
- return !DocumentLinksButton.StartLink ? null : (
+ getLinkUI = () =>
+ !DocumentLinksButton.StartLink ? null : (
<span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
<span className="bottomPopup-text">
Creating link from:{' '}
@@ -108,7 +114,7 @@ export class CollectionLinearView extends CollectionSubView() {
</b>
</span>
- <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top">
+ <Tooltip title={<div className="dash-tooltip">Toggle description pop-up </div>} placement="top">
<span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'}
</span>
@@ -121,9 +127,8 @@ export class CollectionLinearView extends CollectionSubView() {
</Tooltip>
</span>
);
- };
- getCurrentlyPlayingUI = () => {
- return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
+ getCurrentlyPlayingUI = () =>
+ !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
<span className="bottomPopup-background">
<span className="bottomPopup-text">
Currently playing:
@@ -139,7 +144,6 @@ export class CollectionLinearView extends CollectionSubView() {
</span>
</span>
);
- };
getDisplayDoc = (doc: Doc, preview: boolean = false) => {
// hack to avoid overhead of making UndoStack,etc into DocumentView style Boxes. If the UndoStack is ever intended to become part of the persisten state of the dashboard, then this would have to change.
@@ -149,6 +153,7 @@ export class CollectionLinearView extends CollectionSubView() {
case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
case '<UndoStack>': return <UndoStack key={doc[Id]}/>;
case '<Branching>': return Doc.UserDoc().isBranchingMode ? <BranchingTrailManager key={doc[Id]} /> : null;
+ default:
}
const nested = doc._type_collection === CollectionViewType.Linear;
@@ -161,7 +166,9 @@ export class CollectionLinearView extends CollectionSubView() {
<div
className={preview ? 'preview' : `collectionLinearView-docBtn`}
key={doc[Id]}
- ref={r => (dref = r || undefined)}
+ ref={r => {
+ dref = r || undefined;
+ }}
style={{
pointerEvents: 'all',
width: NumCast(doc._width),
@@ -194,7 +201,7 @@ export class CollectionLinearView extends CollectionSubView() {
childFilters={this._props.childFilters}
childFiltersByRanges={this._props.childFiltersByRanges}
searchFilterDocs={this._props.searchFilterDocs}
- hideResizeHandles={true}
+ hideResizeHandles
/>
</div>
);
@@ -220,30 +227,26 @@ export class CollectionLinearView extends CollectionSubView() {
ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
}}
tooltip={isExpanded ? 'Close' : 'Open'}
- fillWidth={true}
- align={'center'}
+ fillWidth
+ align="center"
/>
);
return (
<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' }}>
- {
- <>
- {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
- {!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>
- )}
- </>
- }
+ {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
+ {!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/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
index ff73e14ae..ab3b4b0b5 100644
--- a/src/client/views/collections/collectionLinear/index.ts
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -1 +1 @@
-export * from "./CollectionLinearView"; \ No newline at end of file
+export * from './CollectionLinearView';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 125dd2781..b8509a005 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Button, IconButton } from 'browndash-components';
@@ -7,7 +9,7 @@ import * as React from 'react';
import { FaChevronRight } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -16,6 +18,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionMulticolumnView.scss';
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
+import { dropActionType } from '../../../util/DropActionTypes';
interface WidthSpecifier {
magnitude: number;
@@ -80,7 +83,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
- this.childLayouts.map(layout => {
+ this.childLayouts.forEach(layout => {
const unit = StrCast(layout._dimUnit, '*');
const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -140,6 +143,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
}
+ return undefined;
}
/**
@@ -159,6 +163,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
}
+ return undefined;
}
/**
@@ -175,7 +180,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* or the ratio width evaluated to a pixel value
*/
private lookupPixels = (layout: Doc): number => {
- const columnUnitLength = this.columnUnitLength;
+ const { columnUnitLength } = this;
if (columnUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
@@ -193,19 +198,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- const columnUnitLength = this.columnUnitLength;
+ const { columnUnitLength } = this;
if (columnUnitLength === undefined) {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
- var xf = Transform.Identity();
- this.childLayouts.map(candidate => {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
+ return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
}
offset += this.lookupPixels(candidate) + resizerWidth;
- });
- return xf;
+ }
+ return Transform.Identity();
};
@undoBatch
@@ -213,7 +218,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
let curInd = -1;
- de.complete.docDragData?.droppedDocuments.forEach(d => (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) {
@@ -265,7 +272,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
this.lookupIndividualTransform(childLayout)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
.scale(this._props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
return (
<DocumentView
Document={childLayout}
@@ -281,11 +287,11 @@ export class CollectionMulticolumnView extends CollectionSubView() {
dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
onClickScript={this.onChildClickHandler}
onDoubleClickScript={this.onChildDoubleClickHandler}
- suppressSetHeight={true}
+ suppressSetHeight
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
hideDecorationTitle={this._props.childHideDecorationTitle}
fitContentsToBox={this._props.fitContentsToBox}
focus={this._props.focus}
@@ -312,17 +318,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
const collector: JSX.Element[] = [];
this.childLayouts.forEach((layout, i) => {
collector.push(
+ // eslint-disable-next-line react/no-array-index-key
<Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
<div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
{this.getDisplayDoc(layout)}
{this.layoutDoc._chromeHidden ? null : (
- <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
)}
<WidthLabel layout={layout} collectionDoc={this.Document} />
</div>
</Tooltip>,
<ResizeBar
width={resizerWidth}
+ // eslint-disable-next-line react/no-array-index-key
key={'resizer' + i}
styleProvider={this._props.styleProvider}
isContentActive={this._props.isContentActive}
@@ -353,14 +361,29 @@ export class CollectionMulticolumnView extends CollectionSubView() {
{this.contents}
{!this._startIndex ? null : (
<Tooltip title="scroll back">
- <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
- <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+ <div
+ style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }}
+ onClick={action(() => {
+ this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+ })}>
+ <Button
+ tooltip="Scroll back"
+ icon={<FontAwesomeIcon icon="chevron-left" size="lg" />}
+ onClick={action(() => {
+ this._startIndex = Math.max(0, this._startIndex - this.maxShown);
+ })}
+ color={SettingsManager.userColor}
+ />
</div>
</Tooltip>
)}
{this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
<Tooltip title="scroll forward">
- <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <div
+ style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }}
+ onClick={action(() => {
+ this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+ })}>
<IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
</div>
</Tooltip>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 17bf3e50c..3fe3d5343 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
@@ -11,6 +12,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionMultirowView.scss';
import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
+
interface HeightSpecifier {
magnitude: number;
unit: string;
@@ -63,7 +65,7 @@ export class CollectionMultirowView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const heightSpecifiers: HeightSpecifier[] = [];
- this.childLayoutPairs.map(pair => {
+ this.childLayoutPairs.forEach(pair => {
const unit = StrCast(pair.layout._dimUnit, '*');
const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -123,6 +125,7 @@ export class CollectionMultirowView extends CollectionSubView() {
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
}
+ return undefined;
}
/**
@@ -142,6 +145,7 @@ export class CollectionMultirowView extends CollectionSubView() {
if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
}
+ return undefined;
}
/**
@@ -158,13 +162,12 @@ export class CollectionMultirowView extends CollectionSubView() {
* or the ratio width evaluated to a pixel value
*/
private lookupPixels = (layout: Doc): number => {
- const rowUnitLength = this.rowUnitLength;
- if (rowUnitLength === undefined) {
+ if (this.rowUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
let height = NumCast(layout._dimMagnitude, this.minimumDim);
if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
- height *= rowUnitLength;
+ height *= this.rowUnitLength;
}
return height;
};
@@ -176,11 +179,11 @@ export class CollectionMultirowView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- const rowUnitLength = this.rowUnitLength;
- if (rowUnitLength === undefined) {
+ if (this.rowUnitLength === undefined) {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
+ // eslint-disable-next-line no-restricted-syntax
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
@@ -195,7 +198,9 @@ export class CollectionMultirowView extends CollectionSubView() {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
let curInd = -1;
- de.complete.docDragData?.droppedDocuments.forEach(d => (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) {
@@ -247,7 +252,6 @@ export class CollectionMultirowView extends CollectionSubView() {
this.lookupIndividualTransform(layout)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
.scale(this._props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
return (
<DocumentView
Document={layout}
@@ -266,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() {
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
hideDecorationTitle={this._props.childHideDecorationTitle}
fitContentsToBox={this._props.fitContentsToBox}
focus={this._props.focus}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index d580d9c52..49ba85524 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -52,27 +53,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
}
};
- private get isActivated() {
- const { toLeft, toRight } = this.props;
- if (toLeft && toRight) {
- if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toLeft) {
- if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toRight) {
- if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- }
- return false;
- }
-
@action
private onPointerUp = () => {
window.removeEventListener('pointermove', this.onPointerMove);
@@ -90,7 +70,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
width: this.props.width,
backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
}}>
- <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
</div>
);
}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index a9579d931..a7a0b3457 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -19,7 +19,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
const getUnit = () => StrCast(layout.dimUnit);
const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
return (
- <div className={'label-wrapper'}>
+ <div className="label-wrapper">
<EditableView
GetValue={getMagnitude}
SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 878c7ff3c..66215f109 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -20,7 +21,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
const getUnit = () => StrCast(layout.dimUnit);
const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
return (
- <div className={'label-wrapper'}>
+ <div className="label-wrapper">
<EditableView
GetValue={getMagnitude}
SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 73d08d5ef..ad77c327d 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -33,7 +34,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
};
private onPointerMove = ({ movementY }: PointerEvent) => {
- const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
+ const { toTop, toBottom, columnUnitLength } = this.props;
const movingDown = movementY > 0;
const toNarrow = movingDown ? toBottom : toTop;
const toWiden = movingDown ? toTop : toBottom;
@@ -50,27 +51,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
}
};
- private get isActivated() {
- const { toTop, toBottom } = this.props;
- if (toTop && toBottom) {
- if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toTop) {
- if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toBottom) {
- if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- }
- return false;
- }
-
@action
private onPointerUp = () => {
window.removeEventListener('pointermove', this.onPointerMove);
@@ -88,7 +68,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
height: this.props.height,
backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
}}>
- <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
</div>
);
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 6a956f2ac..ee79812a1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,18 +1,22 @@
+/* eslint-disable no-restricted-syntax */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, PopupTrigger, Type } from 'browndash-components';
import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
-import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
+import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -28,18 +32,8 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
-const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
-export enum ColumnType {
- Number,
- String,
- Boolean,
- Date,
- Image,
- RTF,
- Enumeration,
- Any,
-}
+const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export const FInfotoColType: { [key: string]: ColumnType } = {
string: ColumnType.String,
@@ -98,12 +92,11 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get _selectedDocs() {
const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
if (!selected.length) {
- for (const sel of SelectionManager.Docs) {
- const contextPath = DocumentManager.GetContextPath(sel, true);
- if (contextPath.includes(this.Document)) {
- const parentInd = contextPath.indexOf(this.Document);
- return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
- }
+ // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window)
+ const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document));
+ if (childOfSchemaDoc) {
+ const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true);
+ return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected
}
}
return selected;
@@ -166,7 +159,9 @@ export class CollectionSchemaView extends CollectionSubView() {
Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
!this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
break;
- case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ case 'update': // let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ break;
+ default:
}
},
true
@@ -195,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() {
if (this._selectedDocs.includes(newDoc)) {
SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
} else {
- this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+ this.addDocToSelection(newDoc, e.shiftKey);
this._selectedCell && (this._selectedCell[0] = newDoc);
this.scrollToDoc(newDoc, {});
}
@@ -214,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDoc = this.sortedDocs.docs[firstIndex - 1];
if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
else {
- this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+ this.addDocToSelection(newDoc, e.shiftKey);
this._selectedCell && (this._selectedCell[0] = newDoc);
this.scrollToDoc(newDoc, {});
}
@@ -243,7 +238,9 @@ export class CollectionSchemaView extends CollectionSubView() {
}
case 'Escape': {
this.deselectCell();
+ break;
}
+ default:
}
}
};
@@ -262,7 +259,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.addNewKey(newKey, defaultVal);
}
- let currKeys = [...this.columnKeys];
+ const currKeys = [...this.columnKeys];
currKeys[index] = newKey;
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -279,13 +276,16 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(0, 0, key);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@action
- addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
+ addNewKey = (key: string, defaultVal: any) =>
+ this.childDocs.forEach(doc => {
+ doc[DocData][key] = defaultVal;
+ });
@undoBatch
removeColumn = (index: number) => {
@@ -295,7 +295,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(index, 1);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -303,7 +303,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
startResize = (e: any, index: number) => {
this._displayColumnWidths = this.storedColumnWidths;
- setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction);
+ setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction);
};
@action
@@ -342,11 +342,11 @@ export class CollectionSchemaView extends CollectionSubView() {
@undoBatch
moveColumn = (fromIndex: number, toIndex: number) => {
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
- let currWidths = this.storedColumnWidths.slice();
+ const currWidths = this.storedColumnWidths.slice();
currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths);
};
@@ -360,7 +360,7 @@ export class CollectionSchemaView extends CollectionSubView() {
document.removeEventListener('pointermove', this.highlightDropColumn);
document.addEventListener('pointermove', this.highlightDropColumn);
- let stopHighlight = (e: PointerEvent) => {
+ const stopHighlight = () => {
document.removeEventListener('pointermove', this.highlightDropColumn);
document.removeEventListener('pointerup', stopHighlight);
};
@@ -413,7 +413,7 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+ addDocToSelection = (doc: Doc, extendSelection: boolean) => {
const rowDocView = DocumentManager.Instance.getDocumentView(doc);
if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
};
@@ -428,19 +428,25 @@ export class CollectionSchemaView extends CollectionSubView() {
const endRow = Math.max(lastSelectedRow, index);
for (let i = startRow; i <= endRow; i++) {
const currDoc = this.sortedDocs.docs[i];
- if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
+ if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true);
}
};
@action
- selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]);
+ selectCell = (doc: Doc, index: number) => {
+ this._selectedCell = [doc, index];
+ };
@action
- deselectCell = () => (this._selectedCell = undefined);
+ deselectCell = () => {
+ this._selectedCell = undefined;
+ };
sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
- setDropIndex = (index: number) => (this._closestDropIndex = index);
+ setDropIndex = (index: number) => {
+ this._closestDropIndex = index;
+ };
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
@@ -484,7 +490,7 @@ export class CollectionSchemaView extends CollectionSubView() {
onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
@action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ onDividerMove = (e: PointerEvent) => {
const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
@@ -514,37 +520,76 @@ export class CollectionSchemaView extends CollectionSubView() {
const rect = found.getBoundingClientRect();
const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
- let focusSpeed = options.zoomTime ?? 50;
+ const focusSpeed = options.zoomTime ?? 50;
smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
return focusSpeed;
}
}
+ return undefined;
};
@computed get fieldDefaultInput() {
switch (this._newFieldType) {
case ColumnType.Number:
- return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="number"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? 0}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
case ColumnType.Boolean:
return (
<>
- <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+ <input
+ type="checkbox"
+ name=""
+ id=""
+ value={this._newFieldDefault}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.checked;
+ })}
+ />
{this._newFieldDefault ? 'true' : 'false'}
</>
);
case ColumnType.String:
- return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="text"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? ''}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
+ default:
+ return undefined;
}
}
onSearchKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Enter':
- this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
+ this._menuKeys.length > 0 && this._menuValue.length > 0
+ ? this.setKey(this._menuKeys[0])
+ : action(() => {
+ this._makeNewField = true;
+ })();
break;
case 'Escape':
this.closeColumnMenu();
break;
+ default:
}
};
@@ -576,7 +621,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeColumnMenu = () => (this._columnMenuIndex = undefined);
+ closeColumnMenu = () => {
+ this._columnMenuIndex = undefined;
+ };
@action
openFilterMenu = (index: number) => {
@@ -585,7 +632,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeFilterMenu = () => (this._filterColumnIndex = undefined);
+ closeFilterMenu = () => {
+ this._filterColumnIndex = undefined;
+ };
openContextMenu = (x: number, y: number, index: number) => {
this.closeColumnMenu();
@@ -615,7 +664,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
};
- getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field);
+ getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] === field);
removeFieldFilters = (field: string) => {
this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove'));
@@ -627,11 +676,14 @@ export class CollectionSchemaView extends CollectionSubView() {
case 'Escape':
this.closeFilterMenu();
break;
+ default:
}
};
@action
- updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value);
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._filterSearchValue = e.target.value;
+ };
@computed get newFieldMenu() {
return (
@@ -640,7 +692,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Number}
+ checked={this._newFieldType === ColumnType.Number}
onChange={action(() => {
this._newFieldType = ColumnType.Number;
this._newFieldDefault = 0;
@@ -652,7 +704,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Boolean}
+ checked={this._newFieldType === ColumnType.Boolean}
onChange={action(() => {
this._newFieldType = ColumnType.Boolean;
this._newFieldDefault = false;
@@ -664,7 +716,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.String}
+ checked={this._newFieldType === ColumnType.String}
onChange={action(() => {
this._newFieldType = ColumnType.String;
this._newFieldDefault = '';
@@ -676,7 +728,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div className="schema-key-warning">{this._newFieldWarning}</div>
<div
className="schema-column-menu-button"
- onPointerDown={action(e => {
+ onPointerDown={action(() => {
if (this.documentKeys.includes(this._menuValue)) {
this._newFieldWarning = 'Field already exists';
} else if (this._menuValue.length === 0) {
@@ -741,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get renderColumnMenu() {
- const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
return (
<div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
<input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
@@ -823,9 +875,9 @@ export class CollectionSchemaView extends CollectionSubView() {
const docs = !field
? this.childDocs
: [...this.childDocs].sort((docA, docB) => {
- const aStr = Field.toString(docA[field] as Field);
- const bStr = Field.toString(docB[field] as Field);
- var out = 0;
+ const aStr = Field.toString(docA[field] as FieldType);
+ const bStr = Field.toString(docB[field] as FieldType);
+ let out = 0;
if (aStr < bStr) out = -1;
if (aStr > bStr) out = 1;
if (desc) out *= -1;
@@ -843,7 +895,7 @@ export class CollectionSchemaView extends CollectionSubView() {
render() {
return (
<div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
- <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
+ <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} />
<div
className="schema-table"
style={{ width: `calc(100% - ${this.previewWidth}px)` }}
@@ -859,7 +911,7 @@ export class CollectionSchemaView extends CollectionSubView() {
placement="right"
background={SettingsManager.userBackgroundColor}
color={SettingsManager.userColor}
- toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+ toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />}
trigger={PopupTrigger.CLICK}
type={Type.TERT}
isOpen={this._columnMenuIndex !== -1 ? false : undefined}
@@ -868,6 +920,7 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this.columnKeys.map((key, index) => (
<SchemaColumnHeader
+ // eslint-disable-next-line react/no-array-index-key
key={index}
columnIndex={index}
columnKeys={this.columnKeys}
@@ -887,28 +940,42 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
- <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDocs
+ schema={this}
+ childDocs={this.sortedDocsFunc}
+ rowHeight={this.rowHeightFunc}
+ setRef={(ref: HTMLDivElement | null) => {
+ this._tableContentRef = ref;
+ }}
+ />
+ }
{this.layoutDoc.chromeHidden ? null : (
<div className="schema-add">
<EditableView
GetValue={returnEmptyString}
SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
placeholder={"Type text to create note or ':' to create specific type"}
- contents={'+ New Node'}
+ contents="+ New Node"
menuCallback={this.menuCallback}
height={CollectionSchemaView._newNodeInputHeight}
/>
</div>
)}
</div>
- {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+ {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />}
{this.previewWidth > 0 && (
- <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+ <div
+ style={{ width: `${this.previewWidth}px` }}
+ ref={ref => {
+ this._previewRef = ref;
+ }}>
{Array.from(this._selectedDocs).lastElement() && (
<DocumentView
Document={Array.from(this._selectedDocs).lastElement()}
fitContentsToBox={returnTrue}
- dontCenter={'y'}
+ dontCenter="y"
onClickScriptDisable="always"
focus={emptyFunction}
defaultDoubleClick={returnIgnore}
@@ -953,7 +1020,10 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
<div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
{this.props.childDocs().docs.map((doc: Doc, index: number) => (
<div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
- <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ }
</div>
))}
</div>
@@ -985,6 +1055,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
return (
<DocumentView
key={this._props.doc[Id]}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props.schema._props}
containerViewPath={this._props.schema.childContainerViewPath}
LayoutTemplate={this._props.schema._props.childLayoutTemplate}
@@ -1004,14 +1075,14 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
searchFilterDocs={this._props.schema.searchFilterDocs}
rootSelected={this._props.schema.rootSelected}
ScreenToLocalTransform={this.screenToLocalXf}
- dragWhenActive={true}
+ dragWhenActive
isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
isContentActive={emptyFunction}
whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
- hideDecorations={true}
- hideTitle={true}
- hideDocumentButtonBar={true}
- hideLinkAnchors={true}
+ hideDecorations
+ hideTitle
+ hideDocumentButtonBar
+ hideLinkAnchors
layout_fitWidth={returnTrue}
/>
);
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 5f8b412be..389fc66b3 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable react/no-unused-prop-types */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Colors } from '../../global/globalEnums';
import './CollectionSchemaView.scss';
@@ -24,8 +26,6 @@ export interface SchemaColumnHeaderProps {
@observer
export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
- @observable _ref: HTMLDivElement | null = null;
-
get fieldKey() {
return this.props.columnKeys[this.props.columnIndex];
}
@@ -34,9 +34,9 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
sortClicked = (e: React.PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- if (this.props.sortField == this.fieldKey && this.props.sortDesc) {
+ if (this.props.sortField === this.fieldKey && this.props.sortDesc) {
this.props.setSort(undefined);
- } else if (this.props.sortField == this.fieldKey) {
+ } else if (this.props.sortField === this.fieldKey) {
this.props.setSort(this.fieldKey, true);
} else {
this.props.setSort(this.fieldKey, false);
@@ -45,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
@action
onPointerDown = (e: React.PointerEvent) => {
- this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+ this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction, false);
};
render() {
@@ -58,19 +58,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
onPointerDown={this.onPointerDown}
ref={col => {
if (col) {
- this._ref = col;
this.props.setColRef(this.props.columnIndex, col);
}
}}>
- <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
+ <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} />
<div className="schema-column-title">{this.fieldKey}</div>
<div className="schema-header-menu">
<div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
<FontAwesomeIcon icon="ellipsis-h" />
</div>
- <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
- <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+ <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField === this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+ <FontAwesomeIcon icon="caret-right" style={this.props.sortField === this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
</div>
</div>
</div>
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 39fea2d2e..61afe08cf 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -5,7 +5,8 @@ import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg';
import { FaExternalLinkAlt } from 'react-icons/fa';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { BoolCast } from '../../../../fields/Types';
import { DragManager } from '../../../util/DragManager';
@@ -62,7 +63,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
}
};
- onPointerEnter = (e: any) => {
+ onPointerEnter = () => {
if (SnappingManager.IsDragging && this._props.isContentActive()) {
document.removeEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointermove', this.onPointerMove);
@@ -74,7 +75,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
if (this._ref && dragIsRow) {
const rect = this._ref.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
+ const y = e.clientY - rect.top; // y position within the element.
const height = this._ref.clientHeight;
const halfLine = height / 2;
if (y <= halfLine) {
@@ -89,7 +90,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
}
};
- onPointerLeave = (e: any) => {
+ onPointerLeave = () => {
if (this._ref) {
this._ref.style.borderTop = '0px';
this._ref.style.borderBottom = '0px';
@@ -130,15 +131,16 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
e,
returnFalse,
emptyFunction,
- undoable(e => {
- e.stopPropagation();
+ undoable(clickEv => {
+ clickEv.stopPropagation();
Doc.toggleLockedPosition(this.Document);
}, 'Delete Row')
)
- }></IconButton>
+ }
+ />
<IconButton
tooltip="close"
- icon={<CgClose size={'16px'} />}
+ icon={<CgClose size="16px" />}
size={Size.XSMALL}
onPointerDown={e =>
setupMoveUpEvents(
@@ -146,8 +148,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
e,
returnFalse,
emptyFunction,
- undoable(e => {
- e.stopPropagation();
+ undoable(clickEv => {
+ clickEv.stopPropagation();
this._props.removeDocument?.(this.Document);
}, 'Delete Row')
)
@@ -163,8 +165,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
e,
returnFalse,
emptyFunction,
- undoable(e => {
- e.stopPropagation();
+ undoable(clickEv => {
+ clickEv.stopPropagation();
this._props.addDocTab(this.Document, OpenWhere.addRight);
}, 'Open schema Doc preview')
)
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bf36b2668..ee30006ae 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable no-use-before-define */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -7,15 +10,17 @@ import * as React from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import Select from 'react-select';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SettingsManager } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
@@ -24,11 +29,11 @@ import { EditableView } from '../../EditableView';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DefaultStyleProvider } from '../../StyleProvider';
import { Colors } from '../../global/globalEnums';
-import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
+import { returnEmptyDocViewList } from '../../nodes/DocumentView';
import { FieldViewProps } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
export interface SchemaTableCellProps {
@@ -62,8 +67,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
makeObservable(this);
}
- static addFieldDoc = (doc: Doc, where: OpenWhere) => {
- DocFocusOrOpen(doc);
+ static addFieldDoc = (doc: Doc | Doc[] /* , where: OpenWhere */) => {
+ DocFocusOrOpen(doc instanceof Doc ? doc : doc[0]);
return true;
};
public static renderProps(props: SchemaTableCellProps) {
@@ -184,7 +189,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
return (
<div
className="schema-table-cell"
- onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
+ onPointerDown={action(() => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
{this.content}
</div>
@@ -204,8 +209,8 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
choosePath(url: URL) {
if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
- if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
+ if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; // Why is this here — good question
const ext = extname(url.href);
return url.href.replace(ext, '_s' + ext);
@@ -220,7 +225,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
.map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
// If there is a path, follow it; otherwise, follow a link to a default image icon
- const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+ const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
return url[0];
}
@@ -244,7 +249,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
};
@action
- removeHoverPreview = (e: React.PointerEvent) => {
+ removeHoverPreview = () => {
if (!this._previewRef) return;
document.body.removeChild(this._previewRef);
};
@@ -256,7 +261,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
- return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
+ return <img src={this.url} width={width || undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
}
}
@@ -280,15 +285,15 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
date && (this._props.Document[this._props.fieldKey] = new DateField(date));
- //}
+ // }
}, 'date change');
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ const { pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<>
<div style={{ pointerEvents: 'none' }}>
- <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} />
+ <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} />
</div>
{pointerEvents === 'none' ? null : (
<Popup
@@ -299,7 +304,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
background={SettingsManager.userBackgroundColor}
popup={
<div style={{ width: 'fit-content', height: '200px' }}>
- <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+ <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
</div>
}
/>
@@ -327,7 +332,7 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
@@ -353,8 +358,8 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
checked={BoolCast(this._props.Document[this._props.fieldKey])}
onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
if ((value?.nativeEvent as any).shiftKey) {
- this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
- } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
+ } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
})}
/>
<EditableView
@@ -389,7 +394,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
const options = this._props.options?.map(facet => ({ value: facet, label: facet }));
return (
<div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 610c2b102..2cf9e4162 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -1,44 +1,44 @@
export enum Colors {
- BLACK = "#000000",
- DARK_GRAY = "#323232",
- MEDIUM_GRAY = "#9F9F9F",
- LIGHT_GRAY = "#DFDFDF",
- WHITE = "#FFFFFF",
- MEDIUM_BLUE = "#4476F7",
- MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
- LIGHT_BLUE = "#BDDDF5",
- PINK = "#E0217D",
- ERROR_RED = "#ff0033",
- YELLOW = "#F5D747",
- DROP_SHADOW = "#32323215",
+ BLACK = '#000000',
+ DARK_GRAY = '#323232',
+ MEDIUM_GRAY = '#9F9F9F',
+ LIGHT_GRAY = '#DFDFDF',
+ WHITE = '#FFFFFF',
+ MEDIUM_BLUE = '#4476F7',
+ MEDIUM_BLUE_ALT = '#4476f73d', // REDUCED OPACITY
+ LIGHT_BLUE = '#BDDDF5',
+ PINK = '#E0217D',
+ ERROR_RED = '#ff0033',
+ YELLOW = '#F5D747',
+ DROP_SHADOW = '#32323215',
}
export enum FontSizes {
- //Bolded
- LARGE_HEADER = "16px",
+ // Bolded
+ LARGE_HEADER = '16px',
- //Bolded or unbolded
- BODY_TEXT = "12px",
+ // Bolded or unbolded
+ BODY_TEXT = '12px',
- //Bolded
- SMALL_TEXT = "9px",
+ // Bolded
+ SMALL_TEXT = '9px',
}
export enum Padding {
- MINIMUM_PADDING = "4px",
- SMALL_PADDING = "8px",
- MEDIUM_PADDING = "16px",
- LARGE_PADDING = "32px",
+ MINIMUM_PADDING = '4px',
+ SMALL_PADDING = '8px',
+ MEDIUM_PADDING = '16px',
+ LARGE_PADDING = '32px',
}
export enum IconSizes {
- ICON_SIZE = "28px",
+ ICON_SIZE = '28px',
}
export enum Borders {
- STANDARD = "solid 1px #9F9F9F"
+ STANDARD = 'solid 1px #9F9F9F',
}
export enum Shadows {
- STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)"
-} \ No newline at end of file
+ STANDARD_SHADOW = '0px 3px 4px rgba(0, 0, 0, 0.3)',
+}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 2a5732708..231bac541 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -2,10 +2,11 @@ import { Colors } from 'browndash-components';
import { action, runInAction } from 'mobx';
import { aggregateBounds } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { Gestures } from '../../../pen-gestures/GestureTypes';
import { DocumentType } from '../../documents/DocumentTypes';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -14,26 +15,29 @@ import { UndoManager, undoable } from '../../util/UndoManager';
import { GestureOverlay } from '../GestureOverlay';
import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
-// import { InkTranscription } from '../InkTranscription';
-import { DocData } from '../../../fields/DocSymbols';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
+import { ImageBox } from '../nodes/ImageBox';
import { VideoBox } from '../nodes/VideoBox';
import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { ImageBox } from '../nodes/ImageBox';
+// import { InkTranscription } from '../InkTranscription';
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function IsNoneSelected() {
return SelectionManager.Views.length <= 0;
}, 'are no document selected');
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
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
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
const selectedViews = SelectionManager.Views;
if (Doc.ActiveTool !== InkTool.None) {
@@ -70,15 +74,18 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
return selected.lastElement()?._backgroundColor ?? 'transparent';
}
SetActiveFillColor(color ?? 'transparent');
- selected.forEach(doc => (doc[DocData].backgroundColor = color));
+ selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore
}
+ return '';
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
return DocumentView.setDefaultTemplate(checkResult);
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
if (checkResult) {
return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
@@ -93,9 +100,11 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
}
+ return undefined;
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined;
if (checkResult) {
@@ -103,19 +112,21 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
return false;
}
selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
['grid', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+ setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; },
}],
['snaplines', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false),
- setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
+ setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; },
}],
['viewAll', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
@@ -127,12 +138,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
}],
['center', {
checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+ setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
}],
['clusters', {
waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
+ setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
}],
]);
@@ -142,35 +153,37 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
setTimeout(() => batch.end(), 100);
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
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),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontFamily'),
}],
['highlight', {
checkResult: () => RichTextMenu.Instance?.fontHighlight,
- setDoc: () => value && RichTextMenu.Instance?.setHighlight(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontHighlight'),
}],
['fontColor', {
checkResult: () => RichTextMenu.Instance?.fontColor,
- setDoc: () => value && RichTextMenu.Instance?.setColor(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontColor'),
}],
['alignment', {
checkResult: () => RichTextMenu.Instance?.textAlign,
- setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+ 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);
+ let fsize = value;
+ if (typeof fsize === 'number') fsize = fsize.toString();
+ if (fsize && Number(fsize).toString() === fsize) fsize += 'px';
+ RichTextMenu.Instance?.setFontField(fsize, 'fontSize');
},
}],
]);
@@ -179,63 +192,55 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
return map.get(attr)?.checkResult();
}
map.get(attr)?.setDoc?.();
+ return undefined;
});
type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
+// eslint-disable-next-line prefer-arrow-callback
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','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
- [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
- (RichTextMenu.Instance?.textAlign === where)):
- where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
- (Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
- RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
+ [ where, { checkResult: () => editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
+ (RichTextMenu.Instance?.textAlign === where)):
+ where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
+ (Doc.UserDoc().textAlign === where),
+ toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle():
+ RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
- (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
+ (Doc.UserDoc().textAlign = where); }
+ }]); // prettier-ignore
// prettier-ignore
const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
[ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false),
toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
- ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
- toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
+ ['dictation', { checkResult: () => !!textView?._recordingDictation,
+ toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }],
['elide', { checkResult: () => false,
toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
- ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (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 ?? false : (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 ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
- toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+ ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')),
+ toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}],
+ ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')),
+ toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}],
+ ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')),
+ 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)();
+ return undefined;
});
-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) {
+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 => {
@@ -301,26 +306,24 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
}
-function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
+function setActiveTool(tool: InkTool | 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
+ ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures)
: false;
}
runInAction(() => {
if (GestureOverlay.Instance) {
GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
}
- if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
+ if (Object.values(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;
+ GestureOverlay.Instance.InkShape = tool as Gestures;
}
} else if (tool) {
// pen or eraser
@@ -334,38 +337,40 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
Doc.ActiveTool = InkTool.None;
}
});
+ return undefined;
}
ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
// prettier-ignore
const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
- setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
+ setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
}],
['labels', {
checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
- setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel),
+ setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; },
setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
}],
['fillColor', {
checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
- setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
+ setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); },
setMode: () => SetActiveFillColor(StrCast(value)),
}],
[ 'strokeWidth', {
checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
- setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+ setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
['strokeColor', {
checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
- setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+ setInk: (doc: Doc) => { doc[DocData].color = String(value); },
setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
]);
@@ -375,11 +380,13 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil
}
map.get(option)?.setMode();
SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
+ return undefined;
});
/** WEB
* webSetURL
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
const selected = SelectionManager.Views.lastElement();
if (selected?.Document.type === DocumentType.WEB) {
@@ -387,30 +394,36 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
}
selected.ComponentView?.setData?.(url);
- //selected.Document.data = new WebField(url);
}
+ return '';
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
if (checkResult) {
return selected?.forward(checkResult) ? undefined : 'lightGray';
}
selected?.forward();
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webBack() {
const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
selected?.back();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function videoSnapshot() {
const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox;
selected?.Snapshot();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function imageSetPixelSize() {
const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
selected?.setNativeSize();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function imageRotate90() {
const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
selected?.rotate();
@@ -418,21 +431,25 @@ ScriptingGlobals.add(function imageRotate90() {
/** Schema
* toggleSchemaPreview
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
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) {
+ return 'transparent';
+ }
+ if (selected) {
if (NumCast(selected.schema_previewWidth) > 0) {
selected.schema_previewWidth = 0;
} else {
selected.schema_previewWidth = 200;
}
}
+ return '';
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
const selected = SelectionManager.Docs.lastElement();
if (checkResult && selected) {
@@ -441,17 +458,20 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
if (selected) {
selected._schema_singleLine = !selected._schema_singleLine;
}
+ return undefined;
});
/** STACK
* groupBy
*/
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
+ SelectionManager.Docs.forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
}
- if (editorView) RichTextMenu.Instance?.setFontFamily(key);
+ if (editorView) RichTextMenu.Instance?.setFontField(key, 'fontFamily');
else Doc.UserDoc().fontFamily = key;
+ return undefined;
});
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 60def5d45..f99a18db2 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,14 +1,17 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
import { Doc, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { Cast, DocCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
import { LinkManager } from '../../util/LinkManager';
import { DocumentView } from '../nodes/DocumentView';
import './LinkMenu.scss';
import { LinkMenuItem } from './LinkMenuItem';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
interface LinkMenuGroupProps {
sourceDoc: Doc;
@@ -22,25 +25,24 @@ interface LinkMenuGroupProps {
@observer
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _menuRef = React.createRef<HTMLDivElement>();
+ @observable _collapsed = false;
getBackgroundColor = (): string | undefined => {
- const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
+ const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
let color: string | undefined;
// if this link's relationship property is not default "link", set its color
- if (link_relationshipList) {
- const relationshipIndex = link_relationshipList.indexOf(this.props.groupType);
+ if (linkRelationshipList) {
+ const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
const RGBcolor: string = linkColorList[relationshipIndex];
if (RGBcolor) {
- //set opacity to 0.25 by modifiying the rgb string
+ // set opacity to 0.25 by modifiying the rgb string
color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)';
}
}
return color;
};
- @observable _collapsed = false;
-
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -76,7 +78,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}>
+ <div
+ className="linkMenu-group-name"
+ onClick={action(() => {
+ this._collapsed = !this._collapsed;
+ })}
+ style={{ background: this.getBackgroundColor() }}>
<p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p>
</div>
{this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a2c9d10b6..303ff4b98 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,16 +1,20 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from '../../util/SelectionManager';
@@ -34,14 +38,14 @@ interface LinkMenuItemProps {
// drag links and drop link targets (embedding them if needed)
export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
- const draggedDocs = (specificLinks ? specificLinks : LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+ const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
if (draggedDocs.length) {
const moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
+ draggedDocs.forEach(async draggedDoc => {
const doc = await Cast(draggedDoc.annotationOn, Doc);
if (doc) moddrag.push(doc);
- }
+ });
const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
dragData.canEmbed = true;
@@ -88,10 +92,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
dragData.dropPropertiesToRemove = ['hidden'];
- DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
+ DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y);
return true;
},
emptyFunction,
@@ -106,7 +110,11 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
- setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
+ setTimeout(
+ action(() => {
+ SettingsManager.Instance.propertiesWidth = 250;
+ })
+ );
}
}
})
@@ -117,10 +125,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const eleClone: any = this._drag.current!.cloneNode(true);
- eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+ eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`;
+ StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]);
this._props.clearLinkEditor?.();
return true;
},
@@ -164,8 +172,12 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
return (
<div
className="linkMenu-item"
- onPointerEnter={action(e => (this._hover = true))}
- onPointerLeave={action(e => (this._hover = false))}
+ onPointerEnter={action(() => {
+ this._hover = true;
+ })}
+ onPointerLeave={action(() => {
+ this._hover = false;
+ })}
style={{
fontSize: this._hover ? 'larger' : undefined,
fontWeight: this._hover ? 'bold' : undefined,
@@ -174,15 +186,15 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
<div className="linkMenu-item-content expand-two">
<div
ref={this._drag}
- className="linkMenu-name" //title="drag to view target. click to customize."
+ className="linkMenu-name" // title="drag to view target. click to customize."
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-item-buttons">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Edit Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Edit Link</div>}>
<div className="linkMenu-icon-wrapper" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="linkMenu-icon" icon="edit" size="sm" />
</div>
</Tooltip>
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Show/Hide Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Show/Hide Link</div>}>
<div className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
<FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
</div>
@@ -211,7 +223,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</p>
) : null}
<div className="linkMenu-title-wrapper">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Follow Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Follow Link</div>}>
<p className="linkMenu-destination-title">
{this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
@@ -221,7 +233,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</div>
<div className="linkMenu-item-buttons">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Delete Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Delete Link</div>}>
<div className="linkMenu-deleteButton" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
</div>
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index c9e3c203d..9fb1c0fdc 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,14 +1,12 @@
-import { action, observable } from 'mobx';
+/* eslint-disable react/require-default-props */
import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { returnEmptyDocViewList } from '../nodes/DocumentView';
import { SearchBox } from '../search/SearchBox';
import './LinkPopup.scss';
@@ -28,16 +26,6 @@ interface LinkPopupProps {
@observer
export class LinkPopup extends React.Component<LinkPopupProps> {
- @observable private linkURL: string = '';
- @observable public view?: EditorView = undefined;
-
- // TODO: should check for valid URL
- @undoBatch
- makeLinkToURL = (target: string, lcoation: string) => ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, OpenWhere.addRight, target, target);
-
- @action
- onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => (this.linkURL = e.target.value);
-
getPWidth = () => 500;
getPHeight = () => 500;
@@ -64,7 +52,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
docViewPath={returnEmptyDocViewList}
linkFrom={linkDoc}
linkCreateAnchor={this.props.linkCreateAnchor}
- linkSearch={true}
+ linkSearch
linkCreated={this.props.linkCreated}
fieldKey="data"
isSelected={returnTrue}
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
deleted file mode 100644
index 0902d53b2..000000000
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import './LinkEditor.scss';
-
-interface link_relationshipSearchProps {
- results: string[] | undefined;
- display: string;
- //callback fn to set rel + hide dropdown upon setting
- handleRelationshipSearchChange: (result: string) => void;
- toggleSearch: () => void;
-}
-@observer
-export class link_relationshipSearch extends React.Component<link_relationshipSearchProps> {
- handleResultClick = (e: React.MouseEvent) => {
- const relationship = (e.target as HTMLParagraphElement).textContent;
- if (relationship) {
- this.props.handleRelationshipSearchChange(relationship);
- }
- };
-
- handleMouseEnter = () => {
- this.props.toggleSearch();
- };
-
- handleMouseLeave = () => {
- this.props.toggleSearch();
- };
-
- /**
- * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
- */
- emptyDiv = () => {
- if (this.props.results && this.props.results.length > 2 && this.props.display === 'block') {
- return <div style={{ height: '50px' }} />;
- }
- };
-
- render() {
- return (
- <div className="linkEditor-relationship-dropdown-container">
- <div className="linkEditor-relationship-dropdown" style={{ display: this.props.display }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
- {
- // return a dropdown of relationship results if there exist results
- this.props.results ? (
- this.props.results.map(result => {
- return (
- <p key={result} onClick={this.handleResultClick}>
- {result}
- </p>
- );
- })
- ) : (
- <p>No matching relationships</p>
- )
- }
- </div>
-
- {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
- {this.emptyDiv()}
- </div>
- );
- }
-}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 12b9870ca..dcbc9fc50 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -2,7 +2,8 @@ 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 { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c685ec66f..9251dca6d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,15 +1,18 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { formatTime } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
@@ -18,11 +21,11 @@ import { undoBatch } from '../../util/UndoManager';
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent';
import './AudioBox.scss';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
import { OpenWhere } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { PresBox } from './trails';
/**
* AudioBox
@@ -42,7 +45,7 @@ declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
}
-export enum media_state {
+export enum mediaState {
PendingRecording = 'pendingRecording',
Recording = 'recording',
Paused = 'paused',
@@ -97,16 +100,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return LinkManager.Links(this.dataDoc);
}
@computed get mediaState() {
- return this.dataDoc.mediaState as media_state;
+ return this.dataDoc.mediaState as mediaState;
+ }
+ set mediaState(value) {
+ this.dataDoc.mediaState = value;
}
@computed get path() {
// returns the path of the audio file
const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
return path === nullAudio ? '' : path;
}
- set mediaState(value) {
- this.dataDoc.mediaState = value;
- }
@computed get timeline() {
return this._stackedTimeline;
@@ -117,17 +120,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._dropDisposer?.();
Object.values(this._disposers).forEach(disposer => disposer?.());
- this.mediaState === media_state.Recording && this.stopRecording();
+ this.mediaState === mediaState.Recording && this.stopRecording();
}
@action
componentDidMount() {
this._props.setContentViewBox?.(this);
if (this.path) {
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
} else {
- this.mediaState = undefined as any as media_state;
+ this.mediaState = undefined as any as mediaState;
}
}
@@ -149,7 +152,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.Document,
this.dataDoc,
this.annotationKey,
- this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+ this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === mediaState.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
undefined,
undefined,
addAsAnnotation
@@ -163,10 +166,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// updates timecode and shows it in timeline, follows links at time
@action
timecodeChanged = () => {
- if (this.mediaState !== media_state.Recording && this._ele) {
+ if (this.mediaState !== mediaState.Recording && this._ele) {
this.links
.map(l => this.getLinkData(l))
- .forEach(({ la1, la2, linkTime }) => {
+ .forEach(({ la1, linkTime }) => {
if (linkTime > NumCast(this.layoutDoc._layout_currentTimecode) && linkTime < this._ele!.currentTime) {
Doc.linkFollowHighlight(la1);
}
@@ -180,7 +183,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._play); // abort any previous clip ending
- if (Number.isNaN(this._ele?.duration)) {
+ if (isNaN(this._ele?.duration ?? Number.NaN)) {
// audio element isn't loaded yet... wait 1/2 second and try again
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this.timeline && this._ele && AudioBox.Enabled) {
@@ -191,7 +194,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) {
this._ele.currentTime = start;
this._ele.play();
- this.mediaState = media_state.Playing;
+ this.mediaState = mediaState.Playing;
this.addCurrentlyPlaying();
this._play = setTimeout(
() => {
@@ -233,7 +236,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// update the recording time
updateRecordTime = () => {
- if (this.mediaState === media_state.Recording) {
+ if (this.mediaState === mediaState.Recording) {
setTimeout(this.updateRecordTime, 30);
if (!this._paused) {
this.layoutDoc._layout_currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000;
@@ -254,7 +257,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
this._recordStart = new Date().getTime();
- runInAction(() => (this.mediaState = media_state.Recording));
+ runInAction(() => {
+ this.mediaState = mediaState.Recording;
+ });
setTimeout(this.updateRecordTime);
this._recorder.start();
setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour
@@ -269,7 +274,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const now = new Date().getTime();
this._paused && (this._pausedTime += now - this._pauseStart);
this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000;
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
this._stream?.getAudioTracks()[0].stop();
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
@@ -277,26 +282,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
// context menu
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const funcs: ContextMenuProps[] = [];
funcs.push({
description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors',
- event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+ event: () => { this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
- event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks),
+ event: () => { this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks}, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
- event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ event: () => { this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick',
- event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+ event: () => { this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
ContextMenu.Instance?.addItem({
@@ -342,9 +347,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
- IsPlaying = () => this.mediaState === media_state.Playing;
+ IsPlaying = () => this.mediaState === mediaState.Playing;
TogglePause = () => {
- if (this.mediaState === media_state.Paused) this.Play();
+ if (this.mediaState === mediaState.Paused) this.Play();
else this.pause();
};
// pause playback without removing from the playback list to allow user to play it again.
@@ -352,7 +357,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
pause = () => {
if (this._ele) {
this._ele.pause();
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
// if paused in the middle of playback, prevents restart on next play
if (!this._finished) clearTimeout(this._play);
@@ -434,7 +439,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
// plays link
- playLink = (link: Doc, options: FocusViewOptions) => {
+ playLink = (link: Doc /* , options: FocusViewOptions */) => {
if (link.annotationOn === this.Document) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
@@ -460,13 +465,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
@action
- timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ timelineWhenChildContentsActiveChanged = (isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ };
timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight);
- setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
+ setPlayheadTime = (time: number) => {
+ this._ele!.currentTime /* = this.layoutDoc._layout_currentTimecode */ = time;
+ };
- playing = () => this.mediaState === media_state.Playing;
+ playing = () => this.mediaState === mediaState.Playing;
isActiveChild = () => this._isAnyChildContentActive;
@@ -497,7 +506,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
e,
returnFalse,
returnFalse,
- action(e => {
+ action(() => {
if (this.timeline?.IsTrimming !== TrimScope.None) {
this.timeline?.CancelTrimming();
} else {
@@ -523,7 +532,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
e,
returnFalse,
returnFalse,
- action((e: PointerEvent, doubleTap?: boolean) => {
+ action((moveEv: PointerEvent, doubleTap?: boolean) => {
if (doubleTap) {
this.startTrim(TrimScope.All);
} else if (this.timeline) {
@@ -563,14 +572,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
setupTimelineDrop = (r: HTMLDivElement | null) => {
if (r && this.timeline) {
this._dropDisposer?.();
- this._dropDisposer = DragManager.MakeDropTarget(
- r,
- (e, de) => {
- const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
- de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
- },
- this.layoutDoc
- );
+ this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData), this.layoutDoc);
}
};
@@ -581,7 +583,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="audiobox-dictation" onPointerDown={this.onFile}>
<FontAwesomeIcon size="2x" icon="file-alt" />
</div>
- {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? (
+ {[mediaState.Recording, mediaState.Playing].includes(this.mediaState) ? (
<div className="recording-controls" onClick={e => e.stopPropagation()}>
<div className="record-button" onPointerDown={this.Record}>
<FontAwesomeIcon size="2x" icon="stop" />
@@ -614,31 +616,29 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="controls-left">
<div
className="audiobox-button"
- title={this.mediaState === media_state.Paused ? 'play' : 'pause'}
+ title={this.mediaState === mediaState.Paused ? 'play' : 'pause'}
onPointerDown={
- this.mediaState === media_state.Paused
+ this.mediaState === mediaState.Paused
? this.Play
: e => {
e.stopPropagation();
this.Pause();
}
}>
- <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? 'play' : 'pause'} size={'1x'} />
+ <FontAwesomeIcon icon={this.mediaState === mediaState.Paused ? 'play' : 'pause'} size="1x" />
</div>
{!this.miniPlayer && (
<>
<Tooltip title={<>trim audio</>}>
<div className="audiobox-button" onPointerDown={this.onClipPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size="1x" />
</div>
</Tooltip>
- {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? (
- <></>
- ) : (
- <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}>
+ {this.timeline?.IsTrimming === TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? null : (
+ <Tooltip title={this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}>
<div className="audiobox-button" onPointerDown={this.onResetPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} />
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size="1x" />
</div>
</Tooltip>
)}
@@ -705,9 +705,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get renderTimeline() {
return (
<CollectionStackedTimeline
- ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
+ ref={action((r: CollectionStackedTimeline | null) => {
+ this._stackedTimeline = r;
+ })}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
- CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
@@ -738,10 +740,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// returns the html audio element
@computed get audio() {
return (
+ // eslint-disable-next-line jsx-a11y/media-has-caption
<audio
ref={this.setRef}
className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
- onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
+ onLoadedData={action(() => {
+ this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration);
+ })}>
<source src={this.path} type="audio/mpeg" />
Not supported.
</audio>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0d0a7c623..6a86af6a7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,8 +1,10 @@
-import { action, makeObservable, observable, trace } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { OmitKeys, numberRange } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { numberRange } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { TransitionTimer } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
@@ -17,7 +19,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
-import { TransitionTimer } from '../../../fields/DocSymbols';
/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -96,7 +97,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
if (this.props.transition && !this.Document[TransitionTimer]) {
const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
this.Document[TransitionTimer] = setTimeout(
- action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+ action(() => {
+ this.Document[TransitionTimer] = this.Transition = undefined;
+ }),
num
);
}
@@ -104,7 +107,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
super.componentDidUpdate(prevProps);
- this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
+ this.WrapperKeys.forEach(
+ action(keys => {
+ (this as any)[keys.upper] = (this.props as any)[keys.lower];
+ })
+ );
}
CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
@@ -120,6 +127,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
case StyleProp.BackgroundColor: return this.BackgroundColor;
case StyleProp.Color: return this.Color;
+ default:
} // prettier-ignore
}
return this._props.styleProvider?.(doc, props, property);
@@ -128,7 +136,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
return CollectionFreeFormDocumentView.animFields.reduce(
(p, val) => {
- p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+ p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce(
+ (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev),
+ undefined as any as number
+ );
return p;
},
{} as { [val: string]: Opt<number> }
@@ -138,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getStringValues(doc: Doc, time: number) {
return CollectionFreeFormDocumentView.animStringFields.reduce(
(p, val) => {
- p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+ p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as any as string);
return p;
},
{} as { [val: string]: Opt<string> }
@@ -178,15 +189,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
docs.forEach(doc => {
if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
- if (!doc['opacity_indexed']) {
+ if (!doc.opacity_indexed) {
// opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
- doc['opacity_indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
+ doc.opacity_indexed = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
}
- CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val)));
- CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
- CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode)));
+ CollectionFreeFormDocumentView.animFields.forEach(val => {
+ doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val);
+ });
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+ doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode);
+ });
+ CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+ doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode);
+ });
const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so
- //doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
+ // doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
targetDoc.activeFrame = ComputedField.MakeFunction('this.embedContainer?._currentFrame||0');
targetDoc.dataTransition = 'inherit';
});
@@ -201,16 +218,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
if (topDoc.z) {
const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
topDoc.z = 0;
- topDoc.x = spt[0];
- topDoc.y = spt[1];
+ [topDoc.x, topDoc.y] = spt;
this._props.removeDocument?.(topDoc);
this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
} else {
const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
const fpt = screenXf.transformPoint(spt[0], spt[1]);
topDoc.z = 1;
- topDoc.x = fpt[0];
- topDoc.y = fpt[1];
+ [topDoc.x, topDoc.y] = fpt;
}
setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
}
@@ -239,6 +254,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
+ localRotation = () => this._props.rotation;
render() {
TraceMobx();
@@ -257,8 +273,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
<div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
DataTransition={this.DataTransition}
+ LocalRotation={this.localRotation}
CollectionFreeFormDocumentView={this.returnThis}
styleProvider={this.styleProvider}
ScreenToLocalTransform={this.screenToLocalTransform}
@@ -271,6 +289,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame);
});
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 9ffdc350d..c1aa1c699 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -2,21 +2,23 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { KeyValueBox } from './KeyValueBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -50,13 +52,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@undoBatch
private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
if (dropEvent.complete.docDragData) {
- const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
- const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
- Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
+ const { droppedDocuments } = dropEvent.complete.docDragData;
+ const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+ Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
}
+ return undefined;
};
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
@@ -66,7 +69,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
e,
this.onPointerMove,
emptyFunction,
- action((e, doubleTap) => {
+ action((moveEv, doubleTap) => {
if (doubleTap) {
this._isAnyChildContentActive = true;
if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
@@ -81,7 +84,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// on click, animate slider movement to the targetWidth
this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
setTimeout(
- action(() => (this._animating = '')),
+ action(() => {
+ this._animating = '';
+ }),
200
);
})
@@ -107,7 +112,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
- /* addAsAnnotation &&*/ this.addDocument(anchor);
+ /* addAsAnnotation && */ this.addDocument(anchor);
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
return anchor;
}
@@ -135,28 +140,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
this.clearDoc(which);
return addDocument(doc);
};
de.canEmbed = true;
- DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
+ DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
- e => this.clearDoc(which)
+ () => this.clearDoc(which)
);
};
docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, 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);
+ moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+ moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+ remDoc1 = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+ remDoc2 = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
/**
* Tests for whether a comparison box slot (ie, before or after) has renderable text content
@@ -164,7 +169,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* @returns a JSX layout string if a text field is found, othwerise undefined
*/
testForTextFields = (whichSlot: string) => {
- const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string';
+ const slotData = Doc.Get(this.dataDoc, whichSlot, true);
+ const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
const layoutTemplateString =
@@ -180,8 +186,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
// The GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
- var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
- if (queryText && queryText.match(/\(\(.*\)\)/)) {
+ const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+ if (queryText?.match(/\(\(.*\)\)/)) {
KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
}
}
@@ -190,17 +196,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
_closeRef = React.createRef<HTMLDivElement>();
render() {
- const clearButton = (which: string) => {
- return (
- <div
- ref={this._closeRef}
- className={`clear-button ${which}`}
- onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
- >
- <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
- </div>
- );
- };
+ const clearButton = (which: string) => (
+ <div
+ ref={this._closeRef}
+ className={`clear-button ${which}`}
+ onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+ >
+ <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
+ </div>
+ );
/**
* Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case
@@ -211,15 +215,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const displayDoc = (whichSlot: string) => {
const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot);
- return targetDoc || layoutTemplateString ? (
+ const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
+ return targetDoc || layoutString ? (
<>
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
- ignoreUsePath={layoutTemplateString ? true : undefined}
+ ignoreUsePath={layoutString ? true : undefined}
renderDepth={this.props.renderDepth + 1}
- LayoutTemplateString={layoutTemplateString}
- Document={layoutTemplateString ? this.Document : targetDoc}
+ LayoutTemplateString={layoutString}
+ Document={layoutString ? this.Document : targetDoc}
containerViewPath={this.DocumentView?.().docViewPath}
moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
@@ -229,10 +234,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider}
- hideLinkButton={true}
+ hideLinkButton
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
- {layoutTemplateString ? null : clearButton(whichSlot)}
+ {layoutString ? null : clearButton(whichSlot)}
</> // placeholder image if doc is missing
) : (
<div className="placeholder">
@@ -240,13 +245,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
);
};
- const displayBox = (which: string, index: number, cover: number) => {
- return (
- <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
- {displayDoc(which)}
- </div>
- );
- };
+ const displayBox = (which: string, index: number, cover: number) => (
+ <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
+ {displayDoc(which)}
+ </div>
+ );
return (
<div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 60c5fdba2..5b1f471d8 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,33 +1,33 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox } from '@mui/material';
import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
+import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { listSpec } from '../../../../fields/Schema';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { SidebarAnnos } from '../../SidebarAnnos';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
-import { PinProps } from '../trails';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import './DataVizBox.scss';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
-import { Checkbox } from '@mui/material';
import { ContextMenu } from '../../ContextMenu';
export enum DataVizView {
@@ -64,9 +64,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -97,7 +97,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
- var records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
}
@@ -112,11 +112,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
@computed.struct get axes() {
return StrListCast(this.layoutDoc._dataViz_axes);
}
- selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+ selectAxes = (axes: string[]) => {
+ this.layoutDoc._dataViz_axes = new List<string>(axes);
+ };
@computed.struct get titleCol() {
return StrCast(this.layoutDoc._dataViz_titleCol);
}
- selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
+ selectTitleCol = (titleCol: string) => {
+ this.layoutDoc._dataViz_titleCol = titleCol;
+ };
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
@@ -126,7 +130,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
- Object.keys(this.layoutDoc).map(key => {
+ Object.keys(this.layoutDoc).forEach(key => {
if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
this.layoutDoc['_' + key] = data[key];
}
@@ -152,7 +156,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
annotationOn: this.Document,
// 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*/
+ /* put in some options */
});
anchor.config_dataViz = this.dataVizView;
anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
@@ -160,24 +164,21 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors);
anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor;
anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors);
- Object.keys(this.layoutDoc).map(key => {
+ Object.keys(this.layoutDoc).forEach(key => {
if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
anchor[key] = this.layoutDoc[key];
}
});
this.addDocument(anchor);
- //addAsAnnotation && this.addDocument(anchor);
+ // addAsAnnotation && this.addDocument(anchor);
return anchor;
};
createNoteAnnotation = () => {
- const createFunc = undoable(
- action(() => {
- const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
- }),
- 'create note annotation'
- );
+ const createFunc = undoable(() => {
+ this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
+ }, 'create note annotation');
if (!this.layoutDoc.layout_showSidebar) {
this.toggleSidebar();
setTimeout(createFunc);
@@ -194,7 +195,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
@computed get SidebarShown() {
- return this.layoutDoc._layout_showSidebar ? true : false;
+ return !!this.layoutDoc._layout_showSidebar;
}
@computed get sidebarHandle() {
return (
@@ -208,7 +209,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -220,7 +221,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
setupMoveUpEvents(
this,
e,
- (e, down, delta) =>
+ (moveEv, down, delta) =>
runInAction(() => {
const localDelta = this._props
.ScreenToLocalTransform()
@@ -248,7 +249,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
options.didMove = true;
this.toggleSidebar();
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
@computed get sidebarWidthPercent() {
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -268,32 +271,31 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
this._disposers.datavis = reaction(
() => {
- if (this.layoutDoc.dataViz_schemaLive == undefined) this.layoutDoc.dataViz_schemaLive = true;
+ if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
- const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
- if (!keys) return;
- const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
- var current: { [key: string]: string }[] = [];
+ const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
+ const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
+ const current: { [key: string]: string }[] = [];
children
.filter(child => child)
.forEach(child => {
const row: { [key: string]: string } = {};
keys.forEach(key => {
- var cell = child[key];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ let cell = child[key];
+ if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
row[key] = StrCast(cell);
});
current.push(row);
});
if (!this.layoutDoc._dataViz_schemaOG) {
// makes a copy of the original table for the "live" toggle
- let csvRows = [];
+ const csvRows = [];
csvRows.push(keys.join(','));
for (let i = 0; i < children.length - 1; i++) {
- let eachRow = [];
+ const eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ let cell = children[i][keys[j]];
+ if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
@@ -307,19 +309,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
}
const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
- const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ const { href } = CsvCast(this.Document[this.fieldKey]).url;
if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
// sets original dataset to the var
const lastRow = current.pop();
DataVizBox.datasetSchemaOG.set(href, current);
current.push(lastRow!);
- fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.datasetSchemaOG.set(href, jsonRes))));
}
return current;
},
current => {
if (current) {
- const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ const { href } = CsvCast(this.Document[this.fieldKey]).url;
if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
}
@@ -331,8 +333,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
fetchData = () => {
if (!this.Document.dataViz_asSchema) {
DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
- fetch('/csvData?uri=' + this.dataUrl?.url.href) //
- .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+ fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
+ .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
}
};
@@ -345,7 +347,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
records: this.records,
axes: this.axes,
titleCol: this.titleCol,
- //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
+ // width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
@@ -353,11 +355,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
- case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
- case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
- case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
+ case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
+ case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
+ case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}}
margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
+ default:
} // prettier-ignore
+ return null;
}
@action
@@ -369,10 +373,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this._marqueeing = [e.clientX, e.clientY];
const target = e.target as any;
if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
+ /* empty */
} else {
// if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
setTimeout(
- action(() => (this._marqueeing = undefined)),
+ action(() => {
+ this._marqueeing = undefined;
+ }),
100
); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
@@ -402,34 +409,44 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
@action
changeLiveSchemaCheckbox = () => {
- this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
- }
+ this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive;
+ };
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
- }
-
-
+ };
askGPT = action(async () => {
GPTPopup.Instance.setSidebarId('data_sidebar');
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
- GPTPopup.Instance.setDataJson("");
+ GPTPopup.Instance.setDataJson('');
GPTPopup.Instance.setMode(GPTPopupMode.DATA);
- let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
- let input = JSON.stringify(data);
+ const data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const input = JSON.stringify(data);
GPTPopup.Instance.setDataJson(input);
GPTPopup.Instance.generateDataAnalysis();
});
render() {
const scale = this._props.NativeDimScaling?.() || 1;
+ const toggleBtn = (name: string, type: DataVizView) => (
+ <Toggle
+ text={name}
+ toggleType={ToggleType.BUTTON}
+ type={Type.SEC}
+ color="black"
+ onClick={() => {
+ this.layoutDoc._dataViz = type;
+ }}
+ toggleStatus={this.layoutDoc._dataViz === type}
+ />
+ );
return !this.records.length ? (
// 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="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-box"
@@ -445,19 +462,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
onWheel={e => e.stopPropagation()}
ref={this._mainCont}>
<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} />
+ {toggleBtn(' TABLE ', DataVizView.TABLE)}
+ {toggleBtn('LINECHART', DataVizView.LINECHART)}
+ {toggleBtn('HISTOGRAM', DataVizView.HISTOGRAM)}
+ {toggleBtn('PIE CHART', DataVizView.PIECHART)}
</div>
- {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
- <div className={'displaySchemaLive'}>
- <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
- <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
- Display Live Updates to Canvas
+ {this.layoutDoc && this.layoutDoc.dataViz_asSchema ? (
+ <div className="displaySchemaLive">
+ <div className="liveSchema-checkBox" style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
</div>
- </div>
) : null}
{this.renderVizView}
@@ -470,7 +487,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
showSidebar={this.SidebarShown}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 24023077f..60bc8df18 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/alt-text */
import { IconButton } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CgClose } from 'react-icons/cg';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
import { DragManager } from '../../../util/DragManager';
@@ -14,56 +17,52 @@ interface SchemaCSVPopUpProps {}
@observer
export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: SchemaCSVPopUp;
- @observable
- public dataVizDoc: Doc | undefined = undefined;
+ @observable public dataVizDoc: Doc | undefined = undefined;
+ @observable public view: DocumentView | undefined = undefined;
+ @observable public target: Doc | undefined = undefined;
+ @observable public visible: boolean = false;
+
+ constructor(props: SchemaCSVPopUpProps) {
+ super(props);
+ makeObservable(this);
+ SchemaCSVPopUp.Instance = this;
+ }
+
@action
public setDataVizDoc = (doc: Doc) => {
this.dataVizDoc = doc;
};
- @observable
- public view: DocumentView | undefined = undefined;
@action
public setView = (docView: DocumentView) => {
this.view = docView;
};
- @observable
- public target: Doc | undefined = undefined;
@action
public setTarget = (doc: Doc) => {
this.target = doc;
};
- @observable
- public visible: boolean = false;
@action
public setVisible = (vis: boolean) => {
this.visible = vis;
};
- constructor(props: SchemaCSVPopUpProps) {
- super(props);
- makeObservable(this);
- SchemaCSVPopUp.Instance = this;
- }
-
- dataBox = () => {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
- {this.heading('Schema Table as Data Visualization Doc')}
- <div className="image-content-wrapper">
- <div className="img-wrapper">
- <div className="img-container" onPointerDown={e => this.drag(e)}>
- <img width={150} height={150} src={'/assets/dataVizBox.png'} />
- </div>
+ dataBox = () => (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('Schema Table as Data Visualization Doc')}
+ <div className="image-content-wrapper">
+ <div className="img-wrapper">
+ <div className="img-container" onPointerDown={e => this.drag(e)}>
+ <img width={150} height={150} src="/assets/dataVizBox.png" />
</div>
</div>
</div>
- );
- };
+ </div>
+ );
heading = (headingText: string) => (
<div className="summary-heading">
@@ -78,24 +77,22 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
setupMoveUpEvents(
{},
e,
- e => {
+ moveEv => {
const sourceAnchorCreator = () => this.dataVizDoc!;
- const targetCreator = (annotationOn: Doc | undefined) => {
+ const targetCreator = () => {
const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
return embedding;
};
- if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
- dragComplete: e => {
- this.setVisible(false);
- },
+ if (this.view && sourceAnchorCreator && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: () => this.setVisible(false),
});
return true;
}
return false;
},
emptyFunction,
- action(e => {})
+ action(() => {})
);
};
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 6672603f3..f0ffdbdcf 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -11,8 +11,9 @@ import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
+import { PinProps } from '../../../DocComponent';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
import './Chart.scss';
@@ -63,14 +64,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@computed get _histogramData() {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
- var ax0 = this._props.axes[0];
- if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
+ const ax0 = this._props.axes[0];
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes[1];
+ const [ax0, ax1] = this._props.axes;
if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
@@ -81,11 +81,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const [ax0, ax1] = this._props.axes;
if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
return ax0 + ' Histogram';
- } else return ax0 + ' by ' + ax1 + ' Histogram';
+ }
+ return ax0 + ' by ' + ax1 + ' Histogram';
}
@computed get parentViz() {
@@ -111,8 +111,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
);
}
- @action
- restoreView = (data: Doc) => {};
+ restoreView = () => {};
// 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({
@@ -132,36 +131,36 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
return !field
? []
: validData.map((d: { [x: string]: any }) =>
!this.numericalXData //
? d[field]
- : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+ : +d[field!].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')
);
};
// 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;
+ let sameAsCurrent: boolean;
+ let 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];
+ let 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]) {
+ 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;
+ 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;
@@ -184,16 +183,16 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const xAxisTitle = Object.keys(dataSet[0])[0];
const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
const 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;
+ let numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
+ let translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
if (numBins > this.maxBins) numBins = this.maxBins;
const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
// converts data into Objects
- var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ let histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
if (!this.numericalXData) {
- var histStringDataSet: { [x: string]: unknown }[] = [];
+ const 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] });
@@ -203,15 +202,15 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
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;
+ const 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
+ const svg = (this._histogramSvg = d3
.select(this._histogramRef.current)
.append('svg')
.attr('class', 'graph')
@@ -219,23 +218,21 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.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
+ let x = d3
.scaleLinear()
.domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
.range([0, width]);
- var histogram = d3
+ const histogram = d3
.histogram()
- .value(function (d) {
- return d;
- })
+ .value(d => 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;
+ const bins = histogram(data);
+ let eachRectWidth = width / bins.length;
+ const 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;
+ let xAxis;
// more calculations based on bins
// x-axis
@@ -244,9 +241,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
// uniqueArr.sort()
histDataSet.sort();
for (let i = 0; i < data.length; i++) {
- var index = 0;
+ let index = 0;
for (let j = 0; j < uniqueArr.length; j++) {
- if (uniqueArr[j] == data[i]) {
+ if (uniqueArr[j] === data[i]) {
index = j;
}
}
@@ -254,7 +251,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
bins.pop();
eachRectWidth = width / bins.length;
- bins.forEach(d => (d.x0 = d.x0!));
+ bins.forEach(d => {
+ d.x0 = d.x0!;
+ });
xAxis = d3
.axisBottom(x)
.ticks(bins.length > 1 ? bins.length - 1 : 1)
@@ -264,12 +263,12 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
x.domain([0, bins.length - 1]);
translateXAxis = eachRectWidth / 2;
} else {
- var allSame = true;
- for (var i = 0; i < bins.length; i++) {
+ let allSame = true;
+ for (let i = 0; i < bins.length; i++) {
if (bins[i] && bins[i][0]) {
- var compare = bins[i][0];
+ const compare = bins[i][0];
for (let j = 1; j < bins[i].length; j++) {
- if (bins[i][j] != compare) allSame = false;
+ if (bins[i][j] !== compare) allSame = false;
}
}
}
@@ -278,8 +277,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
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();
+ const tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
+ const curDomain = x.domain();
x.domain([curDomain[0], curDomain[0] + tickDiff * bins.length]);
}
@@ -287,16 +286,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
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]);
+ const maxFrequency = this.numericalYData ?
+ d3.max(histDataSet, (d: any) => (d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '')
+ .replace(/%/g, '').replace(/</g, '')) : 0)) :
+ d3.max(bins, d => d.length); // prettier-ignore
+ const y = d3.scaleLinear().range([height, 0]);
y.domain([0, +maxFrequency!]);
- var yAxis = d3.axisLeft(y).ticks(maxFrequency!);
+ const yAxis = d3.axisLeft(y).ticks(maxFrequency!);
if (this.numericalYData) {
const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0);
yAxisCreator(svg.append('g'), width, yScale);
@@ -311,18 +307,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
const onHover = action((e: any) => {
this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
+ // eslint-disable-next-line no-use-before-define
updateHighlights();
});
- const mouseOut = action((e: any) => {
+ const mouseOut = action(() => {
this.hoverOverData = undefined;
+ // eslint-disable-next-line no-use-before-define
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';
- });
+ const { hoverOverData: hoverOverBar, selectedData } = this;
+ svg.selectAll('rect').attr('class', (d: any) => ((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);
@@ -332,7 +327,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.style('text-anchor', 'middle')
.text(xAxisTitle);
svg.append('text')
- .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('transform', 'rotate(-90) translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
.attr('y', -20)
.style('text-anchor', 'middle')
@@ -340,7 +335,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
d3.format('.0f');
// draw bars
- var selected = this.selectedData;
+ const selected = this.selectedData;
svg.selectAll('rect')
.data(bins)
.enter()
@@ -348,49 +343,34 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.attr(
'transform',
this.numericalYData
- ? function (d) {
- const eachData = histDataSet.filter((data: { [x: string]: number }) => {
- return data[xAxisTitle] == d[0];
- });
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ ? d => {
+ const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+ const 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) + ')';
- }
+ : d => 'translate(' + x(d.x0!) + ',' + y(d.length) + ')'
)
.attr(
'height',
this.numericalYData
- ? function (d) {
- const eachData = histDataSet.filter((data: { [x: string]: number }) => {
- return data[xAxisTitle] == d[0];
- });
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ ? d => {
+ const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+ const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
return height - y(length);
}
- : function (d) {
- return height - y(d.length);
- }
+ : d => 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('class', selected ? d => (selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar') : () => 'histogram-bar')
.attr('fill', d => {
- var barColor;
+ let barColor;
const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
barColors.forEach(each => {
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
else {
const range = StrCast(each[0]).split(' to ');
+ // eslint-disable-next-line prefer-destructuring
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
@@ -400,7 +380,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@action changeSelectedColor = (color: string) => {
this.curBarSelected.attr('fill', color);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
@@ -409,22 +389,24 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@action eraseSelectedColor = () => {
this.curBarSelected.attr('fill', this._props.layoutDoc.dataViz_histogram_defaultColor);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
};
updateBarColors = () => {
- var svg = this._histogramSvg;
+ const svg = this._histogramSvg;
if (svg)
svg.selectAll('rect').attr('fill', (d: any) => {
- var barColor;
+ let barColor;
const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
barColors.forEach(each => {
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
else {
const range = StrCast(each[0]).split(' to ');
+ // eslint-disable-next-line prefer-destructuring
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
@@ -435,39 +417,41 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
render() {
this.updateBarColors();
this._histogramData;
- var curSelectedBarName = '';
- var titleAccessor: any = 'dataViz_histogram_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let curSelectedBarName = '';
+ let titleAccessor: any = 'dataViz_histogram_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
- var selected = 'none';
+ let selected = 'none';
if (this._currSelected) {
- curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
selected = '{ ';
- Object.keys(this._currSelected).forEach(key =>
+ Object.keys(this._currSelected).forEach(key => {
key //
? (selected += key + ': ' + this._currSelected[key] + ', ')
- : ''
- );
+ : '';
+ });
selected = selected.substring(0, selected.length - 2) + ' }';
- if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
- selected+= "\n" + this._props.titleCol + ": "
+ if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
+ selected += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
- if (this._props.axes[1]){
- if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
- }
- else selected+= each[this._props.titleCol] + ", ";
+ if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
+ if (this._props.axes[1]) {
+ if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+ } else selected += each[this._props.titleCol] + ', ';
}
- })
- selected = selected.slice(0,-1).slice(0,-1);
+ });
+ selected = selected.slice(0, -1).slice(0, -1);
}
}
- var selectedBarColor;
- var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
- barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
+ let selectedBarColor;
+ const barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ barColors.forEach(each => {
+ // eslint-disable-next-line prefer-destructuring
+ each[0] === curSelectedBarName && (selectedBarColor = each[1]);
+ });
if (this._histogramData.length > 0 || !this.parentViz) {
return this._props.axes.length >= 1 ? (
@@ -476,45 +460,51 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Default Bar Color'}
+ tooltip="Change Default Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
selectedColor={StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor)}
- setFinalColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
- setSelectedColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+ setFinalColor={undoable(color => {
+ this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+ }, 'Change Default Bar Color')}
+ setSelectedColor={undoable(color => {
+ this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+ }, 'Change Default Bar Color')}
size={Size.XSMALL}
/>
</div>
<div ref={this._histogramRef} />
- {selected != 'none' ? (
- <div className={'selected-data'}>
+ {selected !== 'none' ? (
+ <div className="selected-data">
Selected: {selected}
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Bar Color'}
+ tooltip="Change Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={selectedBarColor ? selectedBarColor : this.curBarSelected.attr('fill')}
+ selectedColor={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'} />}
+ icon={<FontAwesomeIcon icon="eraser" />}
size={Size.XSMALL}
- color={'black'}
+ color="black"
type={Type.SEC}
- tooltip={'Revert to the default bar color'}
+ tooltip="Revert to the default bar color"
onClick={undoable(
action(() => this.eraseSelectedColor()),
'Change Selected Bar Color'
@@ -524,7 +514,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
) : null}
</div>
) : (
- <span className="chart-container"> {'first use table view to select a column to graph'}</span>
+ <span className="chart-container"> first use table view to select a column to graph</span>
);
}
// when it is a brushed table and the incoming table doesn't have any rows selected
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e093ec648..8105adf1e 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -3,7 +3,7 @@ import * as d3 from 'd3';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, NumListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
@@ -11,10 +11,11 @@ import { Docs } from '../../../../documents/Documents';
import { DocumentManager } from '../../../../util/DocumentManager';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
import { DataVizBox } from '../DataVizBox';
import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
import './Chart.scss';
+import { PinProps } from '../../../DocComponent';
export interface DataPoint {
x: number;
@@ -62,7 +63,6 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
@computed get _lineChartData() {
- var guids = StrListCast(this._props.layoutDoc.dataViz_rowIds);
if (this._props.axes.length <= 1) return [];
return this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
}
@@ -102,7 +102,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
);
this._disposers.annos = reaction(
() => DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']),
- annotations => {
+ (/* annotations */) => {
// modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
// could be blue colored to make it look like anchor
// this.drawAnnotations()
@@ -175,7 +175,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
//
- title: 'line doc selection' + this._currSelected?.x,
+ title: 'line doc selection' + (this._currSelected?.x ?? ''),
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
@@ -191,11 +191,12 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const ax0 = this._props.axes[0];
+ const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Line Chart';
- } else return ax1 + ' by ' + ax0 + ' Line Chart';
+ }
+ return ax1 + ' by ' + ax0 + ' Line Chart';
}
setupTooltip() {
@@ -215,9 +216,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
@action
setCurrSelected(x?: number, y?: number) {
// TODO: nda - get rid of svg element in the list?
- if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = 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.records.forEach(record => record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true));
+ this._props.records.forEach(record => {
+ record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true);
+ });
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -241,13 +244,13 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
- var { xMin, xMax, yMin, yMax } = rangeVals;
+ let { xMin, xMax, yMin, yMax } = rangeVals;
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
return;
}
// adding svg
- const margin = this._props.margin;
+ const { margin } = this._props;
const svg = (this._lineChartSvg = d3
.select(this._lineChartRef.current)
.append('svg')
@@ -257,18 +260,19 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`));
- var validSecondData;
- if (this._props.axes.length>2){ // for when there are 2 lines on the chart
- var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+ let validSecondData;
+ if (this._props.axes.length > 2) {
+ // for when there are 2 lines on the chart
+ const next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
validSecondData = next.filter(d => {
- if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+ if (!d.x || isNaN(d.x) || !d.y || isNaN(d.y)) return false;
return true;
});
- var secondDataRange = minMaxRange([validSecondData]);
- if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
- if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
- if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
- if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+ const secondDataRange = minMaxRange([validSecondData]);
+ if (secondDataRange.xMax! > xMax) xMax = secondDataRange.xMax;
+ if (secondDataRange.yMax! > yMax) yMax = secondDataRange.yMax;
+ if (secondDataRange.xMin! < xMin) xMin = secondDataRange.xMin;
+ if (secondDataRange.yMin! < yMin) yMin = secondDataRange.yMin;
}
// creating the x and y scales
@@ -285,40 +289,34 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
if (validSecondData) {
drawLine(svg.append('path'), validSecondData, lineGen, true);
this.drawDataPoints(validSecondData, 0, xScale, yScale);
- svg.append('path').attr("stroke", "red");
+ svg.append('path').attr('stroke', 'red');
// legend
- var color = d3.scaleOrdinal()
- .range(["black", "blue"])
- .domain([this._props.axes[1], this._props.axes[2]])
- svg.selectAll("mydots")
+ const color: any = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+ svg.selectAll('mydots')
.data([this._props.axes[1], this._props.axes[2]])
.enter()
- .append("circle")
- .attr("cx", 5)
- .attr("cy", function(d,i){ return -30 + i*15})
- .attr("r", 7)
- .style("fill", function(d){ return color(d)})
- svg.selectAll("mylabels")
+ .append('circle')
+ .attr('cx', 5)
+ .attr('cy', (d, i) => -30 + i * 15)
+ .attr('r', 7)
+ .style('fill', d => color(d));
+ svg.selectAll('mylabels')
.data([this._props.axes[1], this._props.axes[2]])
.enter()
- .append("text")
- .attr("x", 25)
- .attr("y", function(d,i){ return -30 + i*15})
- .style("fill", function(d){ return color(d)})
- .text(function(d){ return d})
- .attr("text-anchor", "left")
- .style("alignment-baseline", "middle")
+ .append('text')
+ .attr('x', 25)
+ .attr('y', (d, i) => -30 + i * 15)
+ .style('fill', d => color(d))
+ .text(d => d)
+ .attr('text-anchor', 'left')
+ .style('alignment-baseline', 'middle');
}
// get valid data points
const data = dataSet[0];
- var validData = data.filter(d => {
- Object.keys(data[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) return false;
- });
- return true;
- });
+ const keys = Object.keys(data[0]);
+ const validData = data.filter(d => !keys.some(key => isNaN(d[key])));
// draw the plot line
drawLine(svg.append('path'), validData, lineGen, false);
@@ -345,7 +343,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis
const d0 = data[x0];
// find .circle-d1 with data-x = d0.x and data-y = d0.y
- const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
+ svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
this.setCurrSelected(d0.x, d0.y);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -368,7 +366,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.style('text-anchor', 'middle')
.text(this._props.axes[0]);
svg.append('text')
- .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('transform', 'rotate(-90) translate(0, -10)')
.attr('x', -(height / 2))
.attr('y', -30)
.attr('height', 20)
@@ -394,57 +392,60 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
render() {
- var titleAccessor: any = 'dataViz_lineChart_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let titleAccessor: any = 'dataViz_lineChart_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += 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';
- var selectedTitle = "";
- if (this._currSelected && this._props.titleCol){
- selectedTitle+= "\n" + this._props.titleCol + ": "
+ let selectedTitle = '';
+ if (this._currSelected && this._props.titleCol) {
+ selectedTitle += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- var mapThisEntry = false;
- if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
- else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
- if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
- })
- selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+ let mapThisEntry = false;
+ if (this._currSelected.x === each[this._props.axes[0]] && this._currSelected.y === each[this._props.axes[1]]) mapThisEntry = true;
+ else if (this._currSelected.y === each[this._props.axes[0]] && this._currSelected.x === each[this._props.axes[1]]) mapThisEntry = true;
+ if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ', ';
+ });
+ selectedTitle = selectedTitle.slice(0, -1).slice(0, -1);
}
- if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
+ if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length === 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
<div className="graph-title">
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
</div>
<div ref={this._lineChartRef} />
- {selectedPt != 'none' ? (
- <div className={'selected-data'}>
+ {selectedPt !== 'none' ? (
+ <div className="selected-data">
{`Selected: ${selectedPt}`}
{`${selectedTitle}`}
<Button
- onClick={e => {
+ onClick={() => {
this._props.vizBox.sidebarBtnDown;
this._props.vizBox.sidebarAddDocument;
- }}></Button>
+ }}
+ />
</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>
+ <span className="chart-container"> first use table view to select two numerical axes to plot</span>
);
+ }
+ 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
index fc23f47de..f57070f92 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -12,8 +12,9 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
import './Chart.scss';
+import { PinProps } from '../../../DocComponent';
export interface PieChartProps {
Document: Doc;
@@ -74,8 +75,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const ax0 = this._props.axes[0];
+ const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Pie Chart';
}
@@ -101,7 +102,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
@action
- restoreView = (data: Doc) => {};
+ 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({
@@ -122,30 +123,30 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
return !field
? undefined
: validData.map((d: { [x: string]: any }) =>
this.byCategory
? d[field] //
- : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '')
+ : +d[field].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '')
);
};
// 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;
+ let index = -1;
+ let 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
+ const p1 = [0, 0]; // center of pie
+ const p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
+ const p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
+ const 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;
+ let 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
@@ -160,13 +161,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
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) {
+ if (lineCrossCount % 2 !== 0) {
// inside the slice of it crosses an odd number of edges
- var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
+ const 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]]
+ ? 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;
@@ -186,104 +187,100 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
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;
+ let percentField = Object.keys(dataSet[0])[0];
+ let descriptionField = Object.keys(dataSet[0])[1]!;
+ const 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 }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ let data = this.data(dataSet);
+ let pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
if (this.byCategory) {
- let uniqueCategories = [...new Set(data)];
- var pieStringDataSet: { frequency: number }[] = [];
+ const uniqueCategories = [...new Set(data)];
+ const 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;
+ // eslint-disable-next-line no-loop-func
+ const sliceData = pieStringDataSet.filter((each: any) => each[percentField] === data[i]);
+ sliceData[0].frequency += 1;
}
pieDataSet = pieStringDataSet;
- percentField = Object.keys(pieDataSet[0])[0];
- descriptionField = Object.keys(pieDataSet[0])[1]!;
+ [percentField, descriptionField] = Object.keys(pieDataSet[0]);
data = this.data(pieStringDataSet);
}
- var trackDuplicates: { [key: string]: any } = {};
- data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+ let trackDuplicates: { [key: string]: any } = {};
+ data.forEach((eachData: any) => {
+ !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+ });
// initial chart
- var svg = (this._piechartSvg = d3
+ const 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);
+ const g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
+ const pie = d3.pie();
+ const arc = d3.arc().innerRadius(0).outerRadius(radius);
+ const updateHighlights = () => {
+ const hoverOverSlice = this.hoverOverData;
+ const { selectedData } = this;
+ svg.selectAll('path').attr('class', (d: any) =>
+ (selectedData && d.startAngle === selectedData.startAngle && d.endAngle === selectedData.endAngle) || (hoverOverSlice && d.startAngle === hoverOverSlice.startAngle && d.endAngle === hoverOverSlice.endAngle) ? 'slice hover' : 'slice'
+ );
+ };
// click/hover
const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
const onHover = action((e: any) => {
this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
updateHighlights();
});
- const mouseOut = action((e: any) => {
+ const mouseOut = action(() => {
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');
+ const selected = this.selectedData;
+ const arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
const possibleDataPointVals: { [x: string]: any }[] = [];
pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => {
- var dataPointVal: { [x: string]: any } = {};
+ const dataPointVal: { [x: string]: any } = {};
dataPointVal[percentField] = each[percentField];
if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
try {
- dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, ''));
- } catch (error) {}
+ dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
+ } catch (error) {
+ /* empty */
+ }
possibleDataPointVals.push(dataPointVal);
});
const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
arcs.append('path')
.attr('fill', (d, i) => {
- var dataPoint;
+ let dataPoint;
const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
- if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+ if (possibleDataPoints.length === 1) [dataPoint] = possibleDataPoints;
else {
dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
}
- var sliceColor;
+ let sliceColor;
if (dataPoint) {
const sliceTitle = dataPoint[this._props.axes[0]];
- const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
+ const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+ sliceColors.forEach(each => {
+ // eslint-disable-next-line prefer-destructuring
+ each[0] === 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('class', selected ? d => (selected && d.startAngle === selected.startAngle && d.endAngle === selected.endAngle ? 'slice hover' : 'slice') : () => 'slice')
// @ts-ignore
.attr('d', arc)
.on('click', onPointClick)
@@ -292,20 +289,22 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// adding labels
trackDuplicates = {};
- data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+ data.forEach((eachData: any) => {
+ !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+ });
arcs.size() < 100 &&
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]);
+ .attr('transform', d => {
+ const centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+ const 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 dataPoint;
+ .text(d => {
+ let dataPoint;
const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
- if (possibleDataPoints.length == 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
+ if (possibleDataPoints.length === 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
else {
dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[trackDuplicates[d.data.toString()]])];
trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
@@ -317,11 +316,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
@action changeSelectedColor = (color: string) => {
this.curSliceSelected.attr('fill', color);
const sliceTitle = this._currSelected[this._props.axes[0]];
- const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
+ const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
- sliceColors.map(each => {
- if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
+ sliceColors.forEach(each => {
+ if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
});
sliceColors.push(StrCast(sliceName + '::' + color));
};
@@ -332,39 +331,39 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- var titleAccessor: any = 'dataViz_pie_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let titleAccessor: any = 'dataViz_pie_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
- var selected: string;
- var curSelectedSliceName = '';
+ let selected: string;
+ let curSelectedSliceName = '';
if (this._currSelected) {
selected = '{ ';
const sliceTitle = this._currSelected[this._props.axes[0]];
- curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- Object.keys(this._currSelected).map(key => {
- key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+ curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+ Object.keys(this._currSelected).forEach(key => {
+ key !== '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
});
selected = selected.substring(0, selected.length - 2);
selected += ' }';
- if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
- selected+= "\n" + this._props.titleCol + ": "
+ if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
+ selected += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
- if (this._props.axes[1]){
- if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
- }
- else selected+= each[this._props.titleCol] + ", ";
+ if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
+ if (this._props.axes[1]) {
+ if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+ } else selected += each[this._props.titleCol] + ', ';
}
- })
- selected = selected.slice(0,-1).slice(0,-1);
+ });
+ selected = selected.slice(0, -1).slice(0, -1);
}
} else selected = 'none';
- var selectedSliceColor;
- var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+ let selectedSliceColor;
+ const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
sliceColors.forEach(each => {
- if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
});
if (this._pieChartData.length > 0 || !this.parentViz) {
@@ -374,30 +373,32 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
</div>
{this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
- <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}>
+ <div className="asHistogram-checkBox" style={{ width: this._props.width }}>
<Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} />
Organize data as histogram
</div>
) : null}
<div ref={this._piechartRef} />
- {selected != 'none' ? (
- <div className={'selected-data'}>
+ {selected !== 'none' ? (
+ <div className="selected-data">
Selected: {selected}
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Slice Color'}
+ tooltip="Change Slice Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')}
+ selectedColor={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}
@@ -406,12 +407,12 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
) : 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>
+ <span className="chart-container"> first use table view to select a column to graph</span>
);
+ }
+ 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 67e1c67bd..b0176f992 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,19 +1,23 @@
+/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { Button, Type } from 'browndash-components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
+import { emptyFunction } from '../../../../../Utils';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast } from '../../../../../fields/Types';
import { DragManager } from '../../../../util/DragManager';
+import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';
-import { undoable } from '../../../../util/UndoManager';
const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
+
interface TableBoxProps {
Document: Doc;
layoutDoc: Doc;
@@ -77,7 +81,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}
@computed get columns() {
- return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : [];
+ return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header !== '' && header !== undefined) : [];
}
// updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
@@ -114,13 +118,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
else highlited?.push(rowId);
if (!selected?.includes(rowId)) selected?.push(rowId);
- } else {
+ } else if (selected?.includes(rowId)) {
// selecting a row
- if (selected?.includes(rowId)) {
- if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
- selected.splice(selected.indexOf(rowId), 1);
- } else selected?.push(rowId);
- }
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ selected.splice(selected.indexOf(rowId), 1);
+ } else selected?.push(rowId);
e.stopPropagation();
this.hasRowsToFilter = selected.length > 0 ? true : false;
};
@@ -131,7 +133,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
setupMoveUpEvents(
{},
e,
- e => {
+ moveEv => {
// dragging off a column to create a brushed DataVizBox
const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
const targetCreator = (annotationOn: Doc | undefined) => {
@@ -145,13 +147,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
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(e.target instanceof HTMLElement ? [e.target] : [], 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.linkDocument.link_displayArrow = true;
+ if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: completeEv => {
+ if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
+ completeEv.linkDocument.link_displayLine = true;
+ completeEv.linkDocument.link_matchEmbeddings = true;
+ completeEv.linkDocument.link_displayArrow = true;
// e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
// e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
@@ -391,7 +393,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
? 'darkgreen'
: this._props.axes.length > 2 && this._props.axes.lastElement() === col
? 'darkred'
- : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+ : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
? 'darkblue'
: undefined,
background: this.settingTitle
@@ -424,11 +426,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- var colSelected = false;
- if (this._props.axes.length > 2) colSelected = this._props.axes[0] == col || this._props.axes[1] == col || this._props.axes[2] == col;
- else if (this._props.axes.length > 1) colSelected = this._props.axes[0] == col || this._props.axes[1] == col;
- else if (this._props.axes.length > 0) colSelected = this._props.axes[0] == col;
- if (this._props.titleCol == col) colSelected = true;
+ let colSelected = false;
+ if (this._props.axes.length > 2) colSelected = this._props.axes[0] === col || this._props.axes[1] === col || this._props.axes[2] === col;
+ else if (this._props.axes.length > 1) colSelected = this._props.axes[0] === col || this._props.axes[1] === col;
+ else if (this._props.axes.length > 0) colSelected = this._props.axes[0] === col;
+ if (this._props.titleCol === col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
@@ -443,10 +445,10 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
</div>
</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>
- );
+ }
+ 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 336935d23..be05c3529 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -5,11 +5,11 @@ import { DataPoint } from '../components/LineChart';
export const minMaxRange = (dataPts: DataPoint[][]) => {
// find the max and min of all the data points
- const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y)));
- const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y)));
+ const yMin = d3.min(dataPts, d => d3.min(d, m => Number(m.y)));
+ const yMax = d3.max(dataPts, d => d3.max(d, m => Number(m.y)));
- const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x)));
- const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x)));
+ const xMin = d3.min(dataPts, d => d3.min(d, m => Number(m.x)));
+ const xMax = d3.max(dataPts, d => d3.max(d, m => Number(m.x)));
return { xMin, xMax, yMin, yMax };
};
@@ -20,18 +20,15 @@ export const scaleCreatorCategorical = (labels: string[], range: number[]) => {
return scale;
};
-export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => {
- return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-};
+export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => {
+export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) =>
// TODO: nda - look into the different types of curves
- return d3
+ d3
.line<DataPoint>()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
.curve(d3.curveMonotoneX);
-};
export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => {
g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
@@ -48,7 +45,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
d3
.axisBottom(scale)
.tickSize(-height)
- .tickFormat((a, b) => '')
+ .tickFormat((/* a, b */) => '')
);
};
@@ -57,10 +54,16 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
d3
.axisLeft(scale)
.tickSize(-width)
- .tickFormat((a, b) => '')
+ .tickFormat((/* a, b */) => '')
);
};
export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
- p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
+ p.datum(dataPts)
+ .attr('fill', 'none')
+ .attr('stroke', 'rgba(53, 162, 235, 0.5)')
+ .attr('stroke-width', 2)
+ .attr('stroke', extra ? 'blue' : 'black')
+ .attr('class', 'line')
+ .attr('d', lineGen);
};
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e729e2fa2..cc4b5b67f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,11 @@
+/* eslint-disable react/require-default-props */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import JsxParser from 'react-jsx-parser';
import * as XRegExp from 'xregexp';
-import { OmitKeys, Without, emptyPath } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
@@ -17,10 +19,8 @@ import { CollectionView } from '../collections/CollectionView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
-import { PresElementBox } from '../nodes/trails/PresElementBox';
+import { PresElementBox } from './trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
-import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
-import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
@@ -71,8 +71,8 @@ interface HTMLtagProps {
children?: JSX.Element[];
}
-//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
-//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
+// "<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
+// "<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
// transform='rotate({2*this.x+this.y}deg)'
// onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } >
// <ImageBox {...props} fieldKey={'data'}/>
@@ -85,7 +85,7 @@ interface HTMLtagProps {
// </HTMLdiv>"
@observer
export class HTMLtag extends React.Component<HTMLtagProps> {
- click = (e: React.MouseEvent) => {
+ click = () => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
};
@@ -96,11 +96,10 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
render() {
const style: { [key: string]: any } = {};
const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
- const replacer = (match: any, expr: string, offset: any, string: any) => {
+ const replacer = (match: any, expr: string) =>
// bcz: this executes a script to convert a property expression string: { script } into a value
- return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
- };
- Object.keys(divKeys).map((prop: string) => {
+ (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+ Object.keys(divKeys).forEach((prop: string) => {
const p = (this.props as any)[prop] as string;
style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
});
@@ -158,7 +157,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
'dontCenter',
'DataTransition',
'contextMenuItems',
- //'onClick', // don't need to omit this since it will be set
+ // 'onClick', // don't need to omit this since it will be set
'onDoubleClickScript',
'onPointerDownScript',
'onPointerUpScript',
@@ -187,21 +186,16 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
let layoutFrame = this.layout;
// replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv>
- const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
- return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
- };
+ const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
- const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
- };
+ const replacer2 = (match: any, p1: string) => `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
// replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag>
- const replacer3 = (match: any, p1: string, offset: any, string: any) => {
- return `</HTMLtag`;
- };
+ const replacer3 = (/* match: any, p1: string, offset: any, string: any */) => `</HTMLtag`;
+
layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
// add onClick function to props
@@ -250,11 +244,9 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
AudioBox,
RecordingBox,
PresBox,
- YoutubeBox,
PresElementBox,
SearchBox,
FunctionPlotBox,
- DashWebRTCVideo,
LinkAnchorBox,
InkingStroke,
LinkBox,
@@ -272,7 +264,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
}}
bindings={bindings}
jsx={layoutFrame}
- showWarnings={true}
+ showWarnings
onError={(test: any) => {
console.log('DocumentContentsView:' + test, bindings, layoutFrame);
}}
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 4a22766cc..364406197 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -3,11 +3,12 @@ import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { factory } from 'typescript';
-import { Field } from '../../../fields/Doc';
+import { FieldType } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
+import { StrCast } from '../../../fields/Types';
import { DocumentManager } from '../../util/DocumentManager';
import { Transformer, ts } from '../../util/Scripting';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { LightboxView } from '../LightboxView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentView } from './DocumentView';
@@ -28,22 +29,22 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
}
render() {
- const view = this._props.view;
- const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
+ const { view } = this._props;
+ const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
return (
<div
className="documentIcon-outerDiv"
- onPointerEnter={action(e => (this._hovered = true))}
- onPointerLeave={action(e => (this._hovered = false))}
+ onPointerEnter={action(() => { this._hovered = true; })} // prettier-ignore
+ onPointerLeave={action(() => { this._hovered = false; })} // prettier-ignore
style={{
pointerEvents: 'all',
opacity: this._hovered ? 0.3 : 1,
position: 'absolute',
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
transform: `translate(${(left + right) / 2}px, ${top}px)`,
}}>
- <Tooltip title={<>{this._props.view.Document.title}</>}>
+ <Tooltip title={<div>{StrCast(this._props.view.Document?.title)}</div>}>
<p>d{this._props.index}</p>
</Tooltip>
</div>
@@ -56,40 +57,40 @@ export class DocumentIconContainer extends React.Component {
public static getTransformer(): Transformer {
const usedDocuments = new Set<number>();
return {
- transformer: context => {
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
+ transformer: context => root => {
+ function visit(nodeIn: ts.Node) {
+ const node = ts.visitEachChild(nodeIn, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- const isntParameter = !ts.isParameter(node.parent);
- if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
- const match = node.text.match(/d([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- const doc = DocumentIcon.DocViews[m].Document;
- usedDocuments.add(m);
- return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
- }
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ const isntParameter = !ts.isParameter(node.parent);
+ if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
+ const match = node.text.match(/d([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ const doc = DocumentIcon.DocViews[m].Document;
+ usedDocuments.add(m);
+ return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
}
}
-
- return node;
}
- return ts.visitNode(root, visit);
- };
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
},
getVars() {
const docs = DocumentIcon.DocViews;
- const capturedVariables: { [name: string]: Field } = {};
- usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`));
+ const capturedVariables: { [name: string]: FieldType } = {};
+ usedDocuments.forEach(index => {
+ capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`;
+ });
return capturedVariables;
},
};
}
render() {
- return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+ return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={dv.DocUniqueId} index={i} view={dv} />);
}
}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2a68d2bf6..9b4e36509 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,23 +1,26 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StopEvent, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { StopEvent, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { Hypothesis } from '../../util/HypothesisUtils';
import { LinkManager } from '../../util/LinkManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { PinProps } from '../DocComponent';
import { ObservableReactComponent } from '../ObservableReactComponent';
import './DocumentLinksButton.scss';
import { DocumentView } from './DocumentView';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
-import { PinProps } from './trails';
-import { DocData } from '../../../fields/DocSymbols';
interface DocumentLinksButtonProps {
View: DocumentView;
@@ -25,20 +28,22 @@ interface DocumentLinksButtonProps {
AlwaysOn?: boolean;
InMenu?: boolean;
OnHover?: boolean;
- StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
+ StartLink?: boolean; // whether the link HAS been started (i.e. now needs to be completed)
ShowCount?: boolean;
scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
hideCount?: () => boolean;
}
export class DocButtonState {
- @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined
+ @observable public StartLink: Doc | undefined = undefined; // origin's Doc, if defined
@observable public StartLinkView: DocumentView | undefined = undefined;
@observable public AnnotationId: string | undefined = undefined;
@observable public AnnotationUri: string | undefined = undefined;
@observable public LinkEditorDocView: DocumentView | undefined = undefined;
+ // eslint-disable-next-line no-use-before-define
public static _instance: DocButtonState | undefined;
public static get Instance() {
+ // eslint-disable-next-line no-return-assign
return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
}
constructor() {
@@ -49,7 +54,7 @@ export class DocButtonState {
export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> {
private _linkButton = React.createRef<HTMLDivElement>();
public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore
- public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore
+ public static set StartLink(value) { runInAction(() => {DocButtonState.Instance.StartLink = value}); } // prettier-ignore
@observable public static StartLinkView: DocumentView | undefined = undefined;
@observable public static AnnotationId: string | undefined = undefined;
@observable public static AnnotationUri: string | undefined = undefined;
@@ -87,12 +92,14 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
this.onLinkButtonMoved,
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && DocumentView.showBackLinks(this._props.View.Document);
}),
undefined,
undefined,
- action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
+ action(() => {
+ DocButtonState.Instance.LinkEditorDocView = this._props.View;
+ })
);
};
@@ -102,9 +109,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
this.onLinkButtonMoved,
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
if (doubleTap && this._props.InMenu && this._props.StartLink) {
- //action(() => Doc.BrushDoc(this._props.View.Document));
+ // action(() => Doc.BrushDoc(this._props.View.Document));
if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -118,7 +125,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
};
@undoBatch
- onLinkClick = (e: React.MouseEvent): void => {
+ onLinkClick = (): void => {
if (this._props.InMenu && this._props.StartLink) {
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
@@ -126,7 +133,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
- //if this LinkButton's Document is undefined
+ // if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this._props.View.Document;
DocumentLinksButton.StartLinkView = this._props.View;
}
@@ -139,7 +146,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
returnFalse,
emptyFunction,
- action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
+ action(clickEv => DocumentLinksButton.finishLinkClick(clickEv.clientX, clickEv.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
);
};
@@ -151,9 +158,11 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- //!this._props.StartLink
+ // !this._props.StartLink
} else if (startLink !== endLink) {
+ // eslint-disable-next-line no-param-reassign
endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+ // eslint-disable-next-line no-param-reassign
startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
@@ -192,7 +201,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
}
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2500
);
}
@@ -242,13 +253,13 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
showLinkCount(this._props.OnHover, this._props.Bottom)
) : (
<div className="documentLinksButton-menu">
- {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ {this._props.StartLink ? ( // if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
<div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
- {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node
- <div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
+ {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( // if the origin node is not this node
+ <div className="documentLinksButton-endLink" ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
@@ -262,7 +273,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
const buttonTitle = 'Tap to view links; double tap to open link collection';
const title = this._props.ShowCount ? buttonTitle : menuTitle;
- //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
+ // render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
<div
className="documentLinksButton-wrapper"
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ee7bbbdba..5962cd09f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,15 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Howl } from 'howler';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../ClientUtils';
+import { Utils, emptyFunction, emptyPath } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -23,14 +27,14 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'
import { DocUtils, Docs } from '../../documents/Documents';
import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
import { FollowLinkScript } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
+import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
@@ -42,6 +46,7 @@ import { FieldsDropdown } from '../FieldsDropdown';
import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
import { AudioAnnoState, StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
@@ -50,12 +55,6 @@ import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
-interface Window {
- MediaRecorder: MediaRecorder;
-}
-declare class MediaRecorder {
- constructor(e: any); // whatever MediaRecorder has
-}
export enum OpenWhereMod {
none = '',
@@ -83,9 +82,6 @@ export enum OpenWhere {
addRightKeyvalue = 'add:right:keyValue',
}
-export function returnEmptyDocViewList() {
- return [] as DocumentView[];
-}
export interface DocumentViewProps extends FieldViewSharedProps {
hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
@@ -122,7 +118,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
* This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
* needing to know about/reference MainView
*/
- public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+ public static addDocTabFunc: (doc: Doc | Doc[], location: OpenWhere) => boolean = returnFalse;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _doubleClickTimeout: NodeJS.Timeout | undefined;
@@ -183,12 +179,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@computed get disableClickScriptFunc() {
const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
- // prettier-ignore
return (
+ // eslint-disable-next-line no-use-before-define
DocumentView.LongPress ||
onScriptDisable === 'always' ||
(onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
- );
+ ); // prettier-ignore
}
@computed get _rootSelected() {
return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
@@ -237,7 +233,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
componentDidMount() {
- runInAction(() => (this._mounted = true));
+ runInAction(() => {
+ this._mounted = true;
+ });
this.setupHandlers();
this._disposers.contentActive = reaction(
() =>
@@ -249,19 +247,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
? true
: undefined,
- active => (this._isContentActive = active),
+ active => {
+ this._isContentActive = active;
+ },
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
() => this.style(this.Document, StyleProp.PointerEvents),
- pointerevents => (this._pointerEvents = pointerevents),
+ pointerevents => {
+ this._pointerEvents = pointerevents;
+ },
{ fireImmediately: true }
);
}
preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData && this.isContentActive() && !this.props.dontRegisterView) {
- dragData.dropAction = dropAction ? dropAction : dragData.dropAction;
+ dragData.dropAction = dropAction || dragData.dropAction;
e.stopPropagation();
}
};
@@ -291,7 +293,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
dragData.removeDocument = this._props.removeDocument;
dragData.moveDocument = this._props.moveDocument;
dragData.draggedViews = [docView];
- dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
+ dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction);
(this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
DragManager.StartDocumentDrag(
selected.map(dv => dv.ContentDiv!),
@@ -311,7 +313,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
const documentView = this._docView;
- if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
let preventDefault = true;
!this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document);
@@ -368,6 +370,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
+ // eslint-disable-next-line no-use-before-define
} else if (!DocumentView.LongPress) {
this._singleClickFunc();
this._singleClickFunc = undefined;
@@ -380,6 +383,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onPointerDown = (e: React.PointerEvent): void => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
+ // eslint-disable-next-line no-use-before-define
this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
@@ -412,7 +416,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onPointerMove = (e: PointerEvent): void => {
if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
- if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+ if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
@@ -430,14 +434,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (this.onPointerUpHandler?.script) {
this.onPointerUpHandler.script.run({ this: this.Document }, console.log);
- } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
- this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+ } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.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
}
+ // eslint-disable-next-line no-use-before-define
if (DocumentView.LongPress) e.preventDefault();
};
- toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
+ toggleFollowLink = undoable((): void => {
const hadOnClick = this.Document.onClick;
this.noOnClick();
this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
@@ -458,16 +463,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}, 'default on click');
deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
- setToggleDetail = undoable(
- (scriptFieldKey: 'onClick') =>
- (this.Document[scriptFieldKey] = ScriptField.MakeScript(
- `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
- .replace('layout_', '')
- .replace(/^layout$/, 'detail')}")`,
- { documentView: 'any' }
- )),
- 'set toggle detail'
- );
+ setToggleDetail = undoable((scriptFieldKey: 'onClick') => {
+ this.Document[scriptFieldKey] = ScriptField.MakeScript(
+ `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+ .replace('layout_', '')
+ .replace(/^layout$/, 'detail')}")`,
+ { documentView: 'any' }
+ );
+ }, 'set toggle detail');
drop = undoable((e: Event, de: DragManager.DropEvent) => {
if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
@@ -505,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const input = document.createElement('input');
input.type = 'file';
input.accept = '.zip';
- input.onchange = _e => {
+ input.onchange = () => {
if (input.files) {
const batch = UndoManager.StartBatch('importing');
Doc.importDocument(input.files[0]).then(doc => {
@@ -523,7 +526,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
- //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
+ // !this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -535,7 +538,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
e.stopPropagation();
e.persist();
- if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - (e?.clientX ?? 0)) > 3 || Math.abs(this._downY - (e?.clientY ?? 0)) > 3)) {
return;
}
}
@@ -587,7 +590,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' });
zorderItems.push({
description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
- event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
+ event: undoBatch(
+ action(() => {
+ this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged;
+ })
+ ),
icon: 'hand-point-up',
});
!zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
@@ -597,7 +604,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
+ onClicks.push({ description: 'Enter Portal', event: undoable(() => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
!Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
if (!this.Document.annotationOn) {
@@ -613,9 +620,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const funcs: ContextMenuProps[] = [];
if (!Doc.noviceMode && this.layoutDoc.onDragStart) {
- funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')) });
- funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: 'Drag Document', icon: 'edit', event: () => (this.layoutDoc.onDragStart = undefined) });
+ funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')); } }); // prettier-ignore
+ funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')); } }); // prettier-ignore
+ funcs.push({ description: 'Drag Document', icon: 'edit', event: () => { this.layoutDoc.onDragStart = undefined; } }); // prettier-ignore
cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' });
}
@@ -624,14 +631,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!Doc.IsSystem(this.Document)) {
if (!Doc.noviceMode) {
moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
- moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
+ moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore
if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
}
- moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
+ moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
}
}
@@ -639,7 +646,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
const constantItems: ContextMenuProps[] = [];
if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
- constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => DocUtils.Zip(this.Document) });
(this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
@@ -655,8 +662,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
!Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' });
!Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' });
- let documentationDescription: string | undefined = undefined;
- let documentationLink: string | undefined = undefined;
+ let documentationDescription: string | undefined;
+ let documentationLink: string | undefined;
switch (this.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
@@ -690,6 +697,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
documentationDescription = 'See DataViz node documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
break;
+ default:
}
// Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant)
if (!this.docContents && documentationDescription && documentationLink) {
@@ -710,8 +718,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
- setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height));
- setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
+ setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore
+ setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
@@ -729,11 +737,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
}
+ default:
}
return this._props.styleProvider?.(doc, props, property);
};
- removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
+ // eslint-disable-next-line no-return-assign
+ removeLinkByHiding = (link: Doc) => () => link.link_displayLine = false; // prettier-ignore
@computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
@@ -748,8 +758,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
layout_showTitle={returnEmptyString}
- hideCaptions={true}
- hideLinkAnchors={true}
+ hideCaptions
+ hideLinkAnchors
layout_fitWidth={returnTrue}
removeDocument={this.removeLinkByHiding(link)}
styleProvider={this.anchorStyleProvider}
@@ -792,32 +802,30 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
- fieldsDropdown = (placeholder: string) => {
- return (
- <div
- ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))}
- onPointerDown={action(e => (this._changingTitleField = true))}
- style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
- <FieldsDropdown
- Document={this.Document}
- placeholder={placeholder}
- selectFunc={action((field: string | number) => {
- if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = field;
- } else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = field;
- }
- this._changingTitleField = false;
- })}
- menuClose={action(() => (this._changingTitleField = false))}
- />
- </div>
- );
- };
+ fieldsDropdown = (placeholder: string) => (
+ <div
+ ref={action((r: any) => { r && (this._titleDropDownInnerWidth = DivWidth(r));} )} // prettier-ignore
+ onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
+ style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+ <FieldsDropdown
+ Document={this.Document}
+ placeholder={placeholder}
+ selectFunc={action((field: string | number) => {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
+ Doc.UserDoc().layout_showTitle = field;
+ }
+ this._changingTitleField = false;
+ })}
+ menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+ />
+ </div>
+ );
/**
* displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
* setting layout_showTitle using the format: field1[:hover]
- **/
+ * */
@computed get titleView() {
const showTitle = this.layout_showTitle?.split(':')[0];
const showTitleHover = this.layout_showTitle?.includes(':hover');
@@ -825,7 +833,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
const background = StrCast(
this.layoutDoc.layout_headingColor,
- StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
+ StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor))
);
const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
@@ -839,7 +847,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
position: this.headerMargin ? 'relative' : 'absolute',
height: this.titleHeight,
width: 100 - sidebarWidthPercent + '%',
- color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
+ color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background),
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
@@ -860,11 +868,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
contents={
showTitle
.split(';')
- .map(field => Field.toJavascriptString(this.Document[field] as Field))
+ .map(field => Field.toJavascriptString(this.Document[field] as FieldType))
.join(' \\ ') || '-unset-'
}
display="block"
- oneLine={true}
+ oneLine
fontSize={(this.titleHeight / 15) * 10}
GetValue={() =>
showTitle
@@ -905,10 +913,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
xPadding={10}
fieldKey={this.layout_showCaption}
styleProvider={this.captionStyleProvider}
- dontRegisterView={true}
+ dontRegisterView
rootSelected={this.rootSelected}
- noSidebar={true}
- dontScale={true}
+ noSidebar
+ dontScale
renderDepth={this._props.renderDepth}
isContentActive={this.isContentActive}
/>
@@ -952,8 +960,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
render() {
TraceMobx();
- const highlighting = this.highlighting;
- const borderPath = this.borderPath;
+ const { highlighting, borderPath } = this;
const boxShadow = !highlighting
? this.boxShadow
: highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
@@ -968,23 +975,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
});
return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={`${DocumentView.ROOT_DIV} docView-hack`}
ref={this._mainCont}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
- onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+ onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+ onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
style={{
borderRadius: this.borderRounding,
pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
}}>
- <>
- {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
- {borderPath?.jsx}
- </>
+ {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])}
+ {borderPath?.jsx}
</div>
);
}
@@ -994,7 +1000,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
* @param presEffectDoc presentation effects document that specifies the animation effect parameters
* @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) {
+ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc> /* , root: Doc */) {
const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
const effectProps = {
left: dir === PresEffectDirection.Left,
@@ -1005,10 +1011,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
delay: 0,
duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
};
- //prettier-ignore
+ // prettier-ignore
switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
- default:
- case PresEffect.None: return renderDoc;
case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
@@ -1016,17 +1020,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+ case PresEffect.None:
+ default: return renderDoc;
}
}
public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
let gumStream: any;
let recorder: any;
- navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
if (audioTextAnnos) audioTextAnnos.push('');
else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
DictationManager.Controls.listen({
- interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+ interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
continuous: { indefinite: false },
}).then(results => {
if (results && [DictationManager.Controls.Infringed].includes(results)) {
@@ -1060,9 +1066,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
@observer
-export class DocumentView extends DocComponent<DocumentViewProps>() {
+export class DocumentView extends DocComponent<DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }>() {
public static ROOT_DIV = 'documentView-effectsWrapper';
- public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore
+ public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore
public ContentRef = React.createRef<HTMLDivElement>();
private _htmlOverlayEffect: Opt<Doc>;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -1084,7 +1090,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
- constructor(props: DocumentViewProps) {
+ constructor(props: DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }) {
super(props);
makeObservable(this);
}
@@ -1161,7 +1167,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
!BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
}
- public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore
+ public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore
public get IsSelected() { return this._selected; } // prettier-ignore
public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore
public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore
@@ -1176,7 +1182,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
}
@computed get anchorViewDoc() {
- return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
+ return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined;
}
@computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
@@ -1213,6 +1219,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+ public get LocalRotation() { return this._props.LocalRotation?.(); } // prettier-ignore
public clearViewTransition = () => {
this._viewTimer && clearTimeout(this._viewTimer);
@@ -1231,18 +1238,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public iconify(finished?: () => void, animateTime?: number) {
this.ComponentView?.updateIcon?.();
const animTime = this._docViewInternal?.animateScaleTime();
- runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
+ runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore
const finalFinished = action(() => {
finished?.();
this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
});
- const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
- if (layout_fieldKey !== 'layout_icon') {
+ const layoutFieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
+ if (layoutFieldKey !== 'layout_icon') {
this.switchViews(true, 'icon', finalFinished);
- if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', '');
+ if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', '');
} else {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true);
+ this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true);
this.Document.deiconifyLayout = undefined;
this._props.bringToFront?.(this.Document);
}
@@ -1262,7 +1269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
autoplay: true,
loop: false,
volume: 0.5,
- onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
+ onend: action(() => { self.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
});
this.dataDoc.audioAnnoState = AudioAnnoState.playing;
break;
@@ -1270,6 +1277,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this.dataDoc[AudioPlay]?.stop();
this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
break;
+ default:
}
}
};
@@ -1284,10 +1292,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this._docViewInternal._animateScaleTime = time;
}
});
- public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+ public setAnimEffect = (presEffect: Doc, timeInMs: number /* , afterTrans?: () => void */) => {
this._animEffectTimer && clearTimeout(this._animEffectTimer);
this.Document[Animation] = presEffect;
- this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+ this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore
};
public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
@@ -1304,7 +1312,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
undoable(() => {
- var tempDoc: Opt<Doc>;
+ let tempDoc: Opt<Doc>;
if (view) {
if (!view.layoutDoc.isTemplateDoc) {
tempDoc = view.Document;
@@ -1322,6 +1330,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
}, 'set default template')();
+ return undefined;
}
/**
@@ -1335,12 +1344,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', '');
if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout;
const defaultLayout = StrCast(this.Document.layout_default);
- if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
+ if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true);
else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
};
public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
const batch = UndoManager.StartBatch('switchView:' + view);
- runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
+ // shrink doc first..
+ runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore
setTimeout(
action(() => {
if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1348,7 +1358,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
} else {
this.setCustomView(custom, view);
}
- this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
+ this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // now expand it
setTimeout(
action(() => {
this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
@@ -1366,7 +1376,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
*/
public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
- layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
+ layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth);
screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
isSelected = () => this.IsSelected;
select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1390,7 +1400,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
NativeDimScaling = () => this.nativeScaling;
- hideLinkCount = () => (this.hideLinkButton ? true : false);
+ hideLinkCount = () => !!this.hideLinkButton;
selfView = () => this;
/**
* @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1413,17 +1423,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
ref={r => {
const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
if (r && val !== this._enableHtmlOverlayTransitions) {
- setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
+ setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore
}
}}
style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
<div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
{DocumentViewInternal.AnimationEffect(
<div className="webBox-textHighlight">
- <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
+ <ObserverJsxParser autoCloseVoidElements key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
</div>,
- { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
- this.Document
+ { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc
)}
</div>
</div>
@@ -1436,7 +1445,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ <div
+ id={this.ViewGuid}
+ className="contentFittingDocumentView"
+ onPointerEnter={action(() => {
+ this._isHovering = true;
+ })}
+ onPointerLeave={action(() => {
+ this._isHovering = false;
+ })}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
@@ -1462,14 +1479,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
layout_fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToContentsTransform}
focus={this._props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
+ ref={action((r: DocumentViewInternal | null) => {
+ r && (this._docViewInternal = r);
+ })}
/>
{this.htmlOverlay()}
{this.ComponentView?.infoUI?.()}
</div>
)}
{/* display link count button */}
- <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
+ <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover Bottom={this.topMost} ShowCount />
</div>
);
}
@@ -1493,7 +1512,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
// shows a stacking view collection (by default, but the user can change) of all documents linked to the source
public static showBackLinks(linkAnchor: Doc) {
- const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+ const docId = ClientUtils.CurrentUserEmail() + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
// prettier-ignore
DocServer.GetRefField(docId).then(docx =>
LightboxView.Instance.SetLightboxDoc(
@@ -1504,19 +1523,27 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
}
+export function returnEmptyDocViewList() {
+ return emptyPath;
+}
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
documentView.iconify();
documentView.select(false);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
- LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
+ LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
dv.toggleDetail(detailLayoutKeySuffix);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
const collectedLinks = DocListCast(linkCollection[DocData].data);
let wid = NumCast(linkSource._width);
@@ -1534,9 +1561,10 @@ 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 && 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;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
const tag = StrCast(collection.title).split('-->')[1];
const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a557cff4f..9c216cba4 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,7 +1,8 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { DivHeight, DivWidth } from '../../../Utils';
+import { DivHeight, DivWidth } from '../../../ClientUtils';
import { Id } from '../../../fields/FieldSymbols';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
@@ -42,7 +43,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._ref.current!.mathField.latex(text);
}
}
- //{ fireImmediately: true }
+ // { fireImmediately: true }
);
reaction(
() => this._props.isSelected(),
@@ -88,7 +89,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
};
@undoBatch
- onChange = (str: string) => (this.dataDoc.text = str);
+ onChange = (str: string) => {
+ this.dataDoc.text = str;
+ };
updateSize = () => {
const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
@@ -111,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
return (
<div
- ref={r => this.updateSize()}
+ ref={() => this.updateSize()}
className="equationBox-cont"
onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
style={{
@@ -122,7 +125,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
fontSize: StrCast(this.layoutDoc._text_fontSize),
}}
onKeyDown={e => e.stopPropagation()}>
- <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+ <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
</div>
);
}
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 46bc6eb03..2b66b83fe 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -8,11 +8,17 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
@observable private opacity = 0;
componentDidMount() {
- setTimeout(() => runInAction(() => (this.opacity = 1)), 500);
+ setTimeout(
+ () =>
+ runInAction(() => {
+ this.opacity = 1;
+ }),
+ 500
+ );
}
render() {
- const rectangle = this.props.rectangle;
+ const { rectangle } = this.props;
return (
<div
style={{
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 771856788..3f453eb93 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable react/no-unused-prop-types */
+/* eslint-disable react/require-default-props */
+import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../fields/DateField';
@@ -5,13 +8,10 @@ import { Doc, Field, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ScriptField } from '../../../fields/ScriptField';
import { WebField } from '../../../fields/URLField';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { Transform } from '../../util/Transform';
-import { ViewBoxInterface } from '../DocComponent';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { PinProps, ViewBoxInterface } from '../DocComponent';
import { DocumentView, OpenWhere } from './DocumentView';
-import { PinProps } from './trails';
-import { computed } from 'mobx';
export interface FocusViewOptions {
willPan?: boolean; // determines whether to pan to target document
@@ -29,10 +29,10 @@ export interface FocusViewOptions {
openLocation?: OpenWhere; // where to open a missing document
zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
toggleTarget?: boolean; // whether to toggle target on and off
- anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
easeFunc?: 'linear' | 'ease'; // transition method for scrolling
}
export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
+// eslint-disable-next-line no-use-before-define
export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -56,7 +56,7 @@ export interface FieldViewSharedProps {
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+ LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view
containerViewPath?: () => DocumentView[];
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
isGroupActive?: () => string | undefined; // is this document part of a group that is active
@@ -75,13 +75,14 @@ export interface FieldViewSharedProps {
onPointerDownScript?: () => ScriptField;
onPointerUpScript?: () => ScriptField;
onBrowseClickScript?: () => ScriptField | undefined;
+ // eslint-disable-next-line no-use-before-define
onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
layout_fitWidth?: (doc: Doc) => boolean | undefined;
searchFilterDocs: () => Doc[];
layout_showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
rootSelected?: () => boolean; // whether the root of a template has been selected
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
@@ -122,7 +123,7 @@ export interface FieldViewProps extends FieldViewSharedProps {
@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 get fieldval() {
return this.props.Document[this.props.fieldKey];
@@ -137,6 +138,6 @@ export class FieldView extends React.Component<FieldViewProps> {
if (field instanceof List) return <div> {field.map(f => Field.toString(f)).join(', ')} </div>;
if (field instanceof WebField) return <p>{Field.toString(field.url.href)}</p>;
if (!(field instanceof Promise)) return <p>{Field.toString(field)}</p>;
- return <p> {'Waiting for server...'} </p>;
+ return <p> Waiting for server... </p>;
}
}
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 1c034bfbe..0d0d7b1c3 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,5 +1,5 @@
-import { Doc } from '../../../../fields/Doc';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../fields/Doc';
import { ButtonType } from './FontIconBox';
export interface IButtonProps {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 57ae92359..1b2aefbe2 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,12 +1,14 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -33,7 +35,7 @@ export enum ButtonType {
NumberSliderButton = 'numSliderBtn',
NumberDropdownButton = 'numDropdownBtn',
NumberInlineButton = 'numInlineBtn',
- EditableText = 'editableText',
+ EditText = 'editableText',
}
export interface ButtonProps extends FieldViewProps {
@@ -82,7 +84,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
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;
+ return null;
}
icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
@@ -108,7 +110,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
* - Color button
* - Dropdown list
* - Number button
- **/
+ * */
_batch: UndoManager.Batch | undefined = undefined;
/**
@@ -117,17 +119,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
@computed get numberDropdown() {
let type: NumberDropdownType;
switch (this.type) {
- case ButtonType.NumberDropdownButton:
- type = 'dropdown';
- break;
- case ButtonType.NumberInlineButton:
- type = 'input';
- break;
+ case ButtonType.NumberDropdownButton: type = 'dropdown'; break;
+ case ButtonType.NumberInlineButton: type = 'input'; break;
case ButtonType.NumberSliderButton:
- default:
- type = 'slider';
+ default: type = 'slider';
break;
- }
+ } // prettier-ignore
const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
// Script for checking the outcome of the toggle
@@ -154,12 +151,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result;
- },
+ () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result,
emptyFunction,
emptyFunction
- );
+ ); // prettier-ignore
return false;
};
@@ -183,12 +178,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
if (selected.length > 1) {
text = selected.length + ' selected';
} else {
- text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+ text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType);
icon = Doc.toIcon(selected.lastElement());
}
return (
<Popup
- icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
+ icon={<FontAwesomeIcon size="1x" icon={icon} />}
text={text}
type={Type.TERT}
color={SettingsManager.userColor}
@@ -274,8 +269,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
// Determine the type of toggle button
const tooltip: string = StrCast(this.Document.toolTip);
- const script = ScriptCast(this.Document.onClick);
- const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
+ // const script = ScriptCast(this.Document.onClick);
+ // const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const items = DocListCast(this.dataDoc.data);
@@ -312,7 +307,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
- const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+ // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
return (
<Toggle
@@ -322,7 +317,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
toggleStatus={toggleStatus}
text={buttonText}
color={color}
- //background={SettingsManager.userBackgroundColor}
+ // background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
onPointerDown={e =>
@@ -331,10 +326,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
e,
returnTrue,
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
(!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
- this._hackToRecompute = this._hackToRecompute + 1;
+ this._hackToRecompute += 1;
})
)
}
@@ -347,27 +342,22 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
*/
@computed get defaultButton() {
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
- const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
const tooltip: string = StrCast(this.Document.toolTip);
return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
}
@computed get editableText() {
- // Script for running the toggle
const script = ScriptCast(this.Document.script);
- // Function to run the script
const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
- const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
-
- return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
+ const setValue = (value: string): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
return (
<div className="menuButton editableText">
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="lock" />
<div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
- <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+ <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
</div>
</div>
);
@@ -383,7 +373,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.NumberDropdownButton:
case ButtonType.NumberInlineButton:
case ButtonType.NumberSliderButton: return this.numberDropdown;
- case ButtonType.EditableText: return this.editableText;
+ case ButtonType.EditText: return this.editableText;
case ButtonType.DropdownList: return this.dropdownListButton;
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
@@ -394,6 +384,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
case ButtonType.MenuButton: return <IconButton {...btnProps} color={color}
background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
+ default:
}
return this.defaultButton;
};
diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
index 09fd6e3ae..76f00b2f4 100644
--- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
+++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
@@ -1,10 +1,11 @@
import * as React from 'react';
-const TrailsIcon = (fill: string) => (
- <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
- <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
- <path
- d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
+function TrailsIcon(fill: string) {
+ return (
+ <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
+ <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
+ <path
+ d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
-104 -104 -156 -191 -201 -334 l-23 -72 0 -3215 c0 -3072 1 -3218 18 -3280 10
-36 39 -108 64 -160 40 -82 59 -107 142 -190 81 -81 111 -103 191 -143 52 -26
122 -55 155 -65 57 -16 322 -17 4775 -20 3250 -2 4736 1 4784 8 256 39 486
@@ -14,68 +15,69 @@ const TrailsIcon = (fill: string) => (
-62 -101 -108 -126 l-42 -22 -4435 -3 c-3954 -2 -4440 0 -4481 13 -26 9 -63
33 -87 56 -79 79 -72 -205 -72 3012 0 2156 3 2889 12 2918 20 70 91 136 168
160 14 4 2010 8 4436 8 3710 1 4418 -1 4456 -13z"
- />
- <path
- d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
+ />
+ <path
+ d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
-1403 24 -38 c13 -21 42 -50 64 -65 l41 -27 816 0 816 0 41 27 c22 15 51 44
64 65 l24 38 0 1425 0 1425 -24 38 c-13 21 -42 50 -64 65 l-41 27 -800 2
c-488 1 -814 -2 -834 -8z"
- />
- <path
- d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
+ />
+ <path
+ d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
-853 24 -38 c13 -21 42 -50 64 -65 l41 -27 1386 0 1386 0 41 27 c22 15 51 44
64 65 l24 38 0 876 0 875 -27 41 c-15 22 -44 51 -65 64 l-38 24 -1370 2 c-847
1 -1383 -2 -1403 -8z"
- />
- <path
- d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
+ />
+ <path
+ d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
-1 c200 -1 307 -15 484 -65 57 -16 107 -29 112 -29 5 0 36 75 69 168 33 92 63
175 67 184 6 14 -10 22 -92 48 -126 39 -308 76 -447 89 -106 11 -337 13 -383
4z"
- />
- <path
- d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
+ />
+ <path
+ d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
-2 8 -89 23 -194 22 -163 29 -190 44 -193 10 -2 91 6 180 17 89 12 258 32 376
46 118 14 216 27 218 28 7 8 -43 391 -52 392 -5 1 -62 -4 -125 -12z"
- />
- <path
- d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
+ />
+ <path
+ d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
-903 24 -38 c13 -21 42 -50 64 -65 l41 -27 926 0 926 0 41 27 c22 15 51 44 64
65 l24 38 0 926 0 925 -27 41 c-15 22 -44 51 -65 64 l-38 24 -910 2 c-557 1
-923 -2 -943 -8z"
- />
- <path
- d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
+ />
+ <path
+ d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
-1 -3 49 -79 110 -167 l110 -161 44 31 c176 126 333 350 418 594 30 86 77 282
77 320 0 8 -57 19 -167 34 -93 13 -182 25 -199 28 -31 5 -31 5 -37 -50z"
- />
- <path
- d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
+ />
+ <path
+ d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
56 -374 59 -377 1 -2 46 4 101 12 159 24 409 45 526 45 l108 0 0 200 0 200
-132 -2 c-73 -1 -151 -3 -173 -5z"
- />
- <path
- d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
+ />
+ <path
+ d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
-5 -7 172 -315 192 -336 4 -4 41 10 82 32 103 55 272 123 414 165 66 20 125
38 132 41 11 4 -4 70 -78 348 -10 39 -14 41 -69 26z"
- />
- <path
- d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
+ />
+ <path
+ d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
359 -13 475 0 49 6 90 12 91 13 2 1 -12 90 -29 197 -26 155 -36 194 -47 192
-8 -2 -85 -6 -170 -9 -160 -6 -357 7 -505 33 -103 18 -104 18 -110 -8z"
- />
- <path
- d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
+ />
+ <path
+ d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
-51 18 -96 21 -98 4 -5 360 79 375 88 7 4 7 24 0 60 -21 109 -7 244 31 307
l20 31 -146 131 c-80 72 -147 131 -149 131 -2 0 -17 -17 -33 -37z"
- />
- <path
- d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
+ />
+ <path
+ d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
244 0 c134 0 244 2 244 5 0 3 -23 33 -51 67 -28 35 -72 98 -97 140 -26 43 -51
77 -57 77 -5 0 -84 -41 -175 -90z"
- />
- </g>
- </svg>
-);
+ />
+ </g>
+ </svg>
+ );
+}
export default TrailsIcon;
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index a86bdbd79..180c651fb 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -2,18 +2,18 @@ import functionPlot from 'function-plot';
import { computed, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
-import { LinkManager } from '../../util/LinkManager';
+import { PresBox } from './trails';
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -89,7 +89,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
- ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
+ // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
if (res) {
const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' });
link && this._props.addDocument?.(link);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bb1f70f97..90b4a6740 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,10 +1,11 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
+import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -14,17 +15,17 @@ import { ObjectField } from '../../../fields/ObjectField';
import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
-import { ContextMenu } from '../../views/ContextMenu';
+import { ContextMenu } from '../ContextMenu';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
@@ -32,26 +33,29 @@ import { StyleProp } from '../StyleProvider';
import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import './ImageBox.scss';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
export class ImageEditorData {
+ // eslint-disable-next-line no-use-before-define
private static _instance: ImageEditorData;
private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
@observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
- @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc });
+ @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
+ this._instance.imageData = { open, rootDoc, source, addDoc };
+ };
constructor() {
makeObservable(this);
ImageEditorData._instance = this;
}
- public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore
- public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore
- public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
- public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
+ public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore
public static set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore
public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore
+ public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
}
@observer
@@ -93,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document);
return anchor;
}
return this.Document;
@@ -106,10 +110,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
selected: this._props.isSelected(),
}),
- ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
+ ({ forceFull, scrSize, selected }) => {
+ this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o';
+ },
{ fireImmediately: true, delay: 1000 }
);
- const layoutDoc = this.layoutDoc;
+ const { layoutDoc } = this;
this._disposers.path = reaction(
() => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
({ nativeSize, width }) => {
@@ -121,10 +127,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
);
this._disposers.scroll = reaction(
() => this.layoutDoc.layout_scrollTop,
- s_top => {
+ sTop => {
this._forcedScroll = true;
- !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top));
- this._mainCont.current?.scrollTo({ top: NumCast(s_top) });
+ !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(sTop));
+ this._mainCont.current?.scrollTo({ top: NumCast(sTop) });
this._forcedScroll = false;
},
{ fireImmediately: true }
@@ -138,7 +144,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
- let added: boolean | undefined = undefined;
+ let added: boolean | undefined;
const targetIsBullseye = (ele: HTMLElement): boolean => {
if (!ele) return false;
if (ele === this._overlayIconRef.current) return true;
@@ -168,7 +174,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
@undoBatch
- resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes);
+ resolution = () => {
+ this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
+ };
@undoBatch
setNativeSize = action(() => {
@@ -189,7 +197,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']);
const w = this.layoutDoc._width;
const h = this.layoutDoc._height;
- this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360;
+ this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360;
this.dataDoc[this.fieldKey + '_nativeWidth'] = nh;
this.dataDoc[this.fieldKey + '_nativeHeight'] = nw;
this.layoutDoc._width = h;
@@ -197,7 +205,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
});
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
const regionData = region[DocData];
regionData.lockedPosition = true;
@@ -223,8 +231,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
croppingProto.type = DocumentType.IMG;
croppingProto.layout = ImageBox.LayoutString('data');
croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
croppingProto.freeform_scale = viewScale;
croppingProto.freeform_scale_min = viewScale;
croppingProto.freeform_panX = anchx / viewScale;
@@ -244,14 +252,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
return cropping;
};
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
- funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+ funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({
description: 'Open Image Editor',
event: action(() => {
@@ -270,7 +278,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (!url?.href) return '';
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
- if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
+ if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
@@ -282,7 +290,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
TraceMobx();
const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
- const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1);
+ const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
return { nativeWidth, nativeHeight, nativeOrientation };
}
@computed get overlayImageIcon() {
@@ -307,7 +315,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
<div
className="imageBox-alternateDropTarget"
ref={this._overlayIconRef}
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
+ onPointerDown={e =>
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
+ this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+ })
+ }
style={{
display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
@@ -324,7 +336,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
- const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+ const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
const altpaths =
alts
?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
@@ -344,8 +356,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
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;
- const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
+ const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
+ const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']);
const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
let transformOrigin = 'center center';
let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
@@ -361,12 +373,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
return (
- <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
+ <div
+ className="imageBox-cont"
+ onPointerEnter={action(() => {
+ this._isHovering = true;
+ })}
+ onPointerLeave={action(() => {
+ this._isHovering = false;
+ })}
+ key={this.layoutDoc[Id]}
+ ref={this.createDropTarget}
+ onPointerDown={this.marqueeDown}>
<div className="imageBox-fader" style={{ opacity: backAlpha }}>
- <img key="paths" src={srcpath} style={{ transform, transformOrigin }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} />
+ <img
+ alt=""
+ key="paths"
+ src={srcpath}
+ style={{ transform, transformOrigin }}
+ onError={action(e => {
+ this._error = e.toString();
+ })}
+ draggable={false}
+ width={nativeWidth}
+ />
{fadepath === srcpath ? null : (
<div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
- <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+ <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
</div>
)}
</div>
@@ -384,14 +416,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!this.dataDoc[this.fieldKey]) {
+ this.chooseImage();
+ } else if (
+ !e.altKey &&
+ e.button === 0 &&
+ NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) &&
+ this._props.isContentActive() &&
+ ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)
+ ) {
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -419,7 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
className="imageBox"
onContextMenu={this.specificContextMenu}
ref={this._mainCont}
- onScroll={action(e => {
+ onScroll={action(() => {
if (!this._forcedScroll) {
if (this.layoutDoc._layout_scrollTop || this._mainCont.current?.scrollTop) {
this._ignoreScroll = true;
@@ -437,6 +476,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}}>
<CollectionFreeFormView
ref={this._ffref}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -444,8 +484,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
styleProvider={this._props.styleProvider}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
+ isAnnotationOverlay
+ annotationLayerHostsContent
PanelWidth={this._props.PanelWidth}
PanelHeight={this._props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
@@ -476,7 +516,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
selectionText={returnEmptyString}
annotationLayer={this._annotationLayer.current}
marqueeContainer={this._mainCont.current}
- highlightDragSrcColor={''}
+ highlightDragSrcColor=""
anchorMenuCrop={this.crop}
/>
)}
@@ -489,7 +529,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
input.type = 'file';
input.multiple = true;
input.accept = 'image/*';
- input.onchange = async _e => {
+ input.onchange = async () => {
const file = input.files?.[0];
if (file) {
const disposer = OverlayView.ShowSpinner();
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 31a2367fc..b8296ce51 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,8 +1,9 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnAlways, returnTrue } from '../../../Utils';
-import { Doc, Field, FieldResult } from '../../../fields/Doc';
+import { returnAlways, returnTrue } from '../../../ClientUtils';
+import { Doc, Field, FieldType, FieldResult } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
@@ -79,7 +80,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
* @param value
* @returns
*/
- public static CompileKVPScript(rawvalue: string): KVPScript | undefined {
+ public static CompileKVPScript(rawvalueIn: string): KVPScript | undefined {
+ let rawvalue = rawvalueIn;
const onDelegate = rawvalue.startsWith('=');
rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
@@ -87,7 +89,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)');
const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`';
- var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
+ let script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
if (!script.compiled) {
script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
}
@@ -96,15 +98,15 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
const { script, type, onDelegate } = kvpScript;
- //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
+ // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
- let field: Field | undefined;
+ let field: FieldType | undefined;
switch (type) {
case 'computed': field = new ComputedField(script); break; // prettier-ignore
case 'script': field = new ScriptField(script); break; // prettier-ignore
default: {
const _setCacheResult_ = (value: FieldResult) => {
- field = value as Field;
+ field = value as FieldType;
if (setResult) setResult?.(value);
else target[key] = field;
};
@@ -153,20 +155,20 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
const ids: { [key: string]: string } = {};
const protos = Doc.GetAllPrototypes(doc);
- for (const proto of protos) {
+ protos.forEach(proto => {
Object.keys(proto).forEach(key => {
if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
ids[key] = key;
}
});
- }
+ });
const rows: JSX.Element[] = [];
let i = 0;
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((a: string, b: string) => {
+ // for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
+ const sortedKeys = keys.sort((a: string, b: string) => {
const a_ = a.split('_')[0];
const b_ = b.split('_')[0];
if (a_ < b_) return -1;
@@ -174,7 +176,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
if (a === a_) return -1;
if (b === b_) return 1;
return a === b ? 0 : a < b ? -1 : 1;
- })) {
+ });
+ sortedKeys.forEach(key => {
rows.push(
<KeyValuePair
doc={realDoc}
@@ -195,7 +198,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
keyName={key}
/>
);
- }
+ });
return rows;
}
@computed get newKeyValue() {
@@ -229,7 +232,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
this._splitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
};
@action
- onDividerUp = (e: PointerEvent): void => {
+ onDividerUp = (): void => {
document.removeEventListener('pointermove', this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
};
@@ -244,11 +247,11 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
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).title}`, _chromeHidden: true });
- for (const row of rows) {
+ rows.forEach(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), rows.lastElement()) : undefined;
@@ -256,22 +259,23 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
const metaKey = row._props.keyName;
- const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
- fieldTemplate.title = metaKey;
- fieldTemplate.layout_fitWidth = true;
- fieldTemplate._xMargin = 10;
- fieldTemplate._yMargin = 10;
- fieldTemplate._width = 100;
- fieldTemplate._height = 40;
- fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
- return fieldTemplate;
+ const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
+ fieldTempDoc.title = metaKey;
+ fieldTempDoc.layout_fitWidth = true;
+ fieldTempDoc._xMargin = 10;
+ fieldTempDoc._yMargin = 10;
+ fieldTempDoc._width = 100;
+ fieldTempDoc._height = 40;
+ fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey);
+ return fieldTempDoc;
};
inferType = (data: FieldResult, metaKey: string) => {
const options = { _width: 300, _height: 300, title: metaKey };
if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
return FormattedTextBox.LayoutString(metaKey);
- } else if (data instanceof List) {
+ }
+ if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
@@ -280,21 +284,18 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
return Docs.Create.StackingDocument([], options);
}
switch (first.data.constructor) {
- case RichTextField:
- return Docs.Create.TreeDocument([], options);
- case ImageField:
- return Docs.Create.MasonryDocument([], options);
- default:
- console.log(`Template for ${first.data.constructor} not supported!`);
- return undefined;
- }
+ case RichTextField: return Docs.Create.TreeDocument([], options);
+ case ImageField: return Docs.Create.MasonryDocument([], options);
+ default: console.log(`Template for ${first.data.constructor} not supported!`);
+ return undefined;
+ } // prettier-ignore
} else if (data instanceof ImageField) {
return ImageBox.LayoutString(metaKey);
}
return new Doc();
};
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const open = cm.findByDescription('Change Perspective...');
const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : [];
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f9e8ce4f3..f96dd2b76 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { Tooltip } from '@mui/material';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Field } from '../../../fields/Doc';
import { DocCast } from '../../../fields/Types';
import { DocumentOptions, FInfo } from '../../documents/Documents';
@@ -62,7 +64,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
render() {
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
- let doc = this._props.doc;
+ let { doc } = this._props;
while (doc) {
if (Object.keys(doc).includes(this._props.keyName)) {
break;
@@ -76,10 +78,18 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
return (
- <tr className={this._props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
+ <tr
+ className={this._props.rowStyle}
+ onPointerEnter={action(() => {
+ this.isPointerOver = true;
+ })}
+ onPointerLeave={action(() => {
+ this.isPointerOver = false;
+ })}>
<td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button
+ type="button"
style={hover}
className="keyValuePair-td-key-delete"
onClick={undoBatch(() => {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 74e78c671..80ece7cc8 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,21 +1,22 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxBaseComponent } from '../DocComponent';
+import { PinProps, ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
+// eslint-disable-next-line import/extensions
import BigText from './LabelBigText';
import './LabelBox.scss';
-import { PinProps, PresBox } from './trails';
-import { Docs } from '../../documents/Documents';
+import { PresBox } from './trails';
@observer
export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -23,7 +24,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
return FieldView.LayoutString(LabelBox, fieldKey);
}
public static LayoutStringWithTitle(fieldStr: string, label?: string) {
- return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; // e.g., "<ImageBox {...props} fieldKey={"data} />"
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
@@ -41,7 +42,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get Title() {
- return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
+ return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -54,14 +55,16 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
get paramsDoc() {
return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc;
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const funcs: ContextMenuProps[] = [];
!Doc.noviceMode &&
funcs.push({
description: 'Clear Script Params',
event: () => {
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
- params?.map(p => (this.paramsDoc[p] = undefined));
+ params?.forEach(p => {
+ this.paramsDoc[p] = undefined;
+ });
},
icon: 'trash',
});
@@ -71,7 +74,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
if (docDragData && missingParams?.includes((e.target as any).textContent)) {
@@ -131,7 +134,10 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
this._timeout = undefined;
if (!r) return params;
- if (!r.offsetHeight || !r.offsetWidth) return (this._timeout = setTimeout(() => this.fitTextToBox(r)));
+ if (!r.offsetHeight || !r.offsetWidth) {
+ this._timeout = setTimeout(() => this.fitTextToBox(r));
+ return this._timeout;
+ }
const parent = r.parentNode;
const parentStyle = parent.style;
parentStyle.display = '';
@@ -154,8 +160,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div
className="labelBox-outerDiv"
- onMouseLeave={action(() => (this._mouseOver = false))}
- onMouseOver={action(() => (this._mouseOver = true))}
+ onMouseLeave={action(() => {
+ this._mouseOver = false;
+ })}
+ // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
+ onMouseOver={action(() => {
+ this._mouseOver = true;
+ })}
ref={this.createDropTarget}
onContextMenu={this.specificContextMenu}
style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index ff1e62885..d43241de0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,11 +1,13 @@
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
+import { Utils, emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { LinkFollower } from '../../util/LinkFollower';
import { SelectionManager } from '../../util/SelectionManager';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -13,7 +15,9 @@ import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkAnchorBox.scss';
import { LinkInfo } from './LinkDocPreview';
+
const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+
@observer
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
@@ -39,14 +43,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
onPointerDown = (e: React.PointerEvent) => {
- const linkSource = this.linkSource;
+ const { linkSource } = this;
linkSource &&
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => {
if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
else this._props.select(false);
});
};
- onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
+ onPointerMove = action((e: PointerEvent) => {
const cdiv = this._ref?.current?.parentElement;
if (!this._isOpen && cdiv) {
const bounds = cdiv.getBoundingClientRect();
@@ -58,16 +62,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
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 {
- this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
- this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
- this.layoutDoc.link_autoMoveAnchors = false;
}
+ this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
+ this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
+ this.layoutDoc.link_autoMoveAnchors = false;
}
return false;
});
- specificContextMenu = (e: React.MouseEvent): void => {};
+ specificContextMenu = (): void => {};
render() {
TraceMobx();
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 3a2509c3d..f01905ee1 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,13 +1,15 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils';
import { FieldResult } from '../../../fields/Doc';
import { DocCss, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -49,15 +51,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._props.setContentViewBox?.(this);
this._disposers.deleting = reaction(
() => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
- empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() =>
- (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
- )), 1000) // prettier-ignore
+ empty => {
+ if (empty) {
+ this._hackToSeeIfDeleted = setTimeout(() => {
+ !this.anchor1 && !this.anchor2 && this._props.removeDocument?.(this.Document);
+ }, 1000);
+ }
+ }
);
this._disposers.dragging = reaction(
() => SnappingManager.IsDragging,
() => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements
- const a = this.anchor1,
- b = this.anchor2;
+ const a = this.anchor1;
+ const b = this.anchor2;
let a1 = a && document.getElementById(a.ViewGuid);
let a2 = b && document.getElementById(b.ViewGuid);
// test whether the anchors themselves are hidden,...
@@ -66,7 +72,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
// .. or whether any of their DOM parents are hidden
for (; a1 && !a1.hidden; a1 = a1.parentElement);
for (; a2 && !a2.hidden; a2 = a2.parentElement);
- this._hide = a1 || a2 ? true : false;
+ this._hide = !!(a1 || a2);
}
})) // prettier-ignore
);
@@ -91,24 +97,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
a.Document[DocCss];
b.Document[DocCss];
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
const bxf = b.screenToViewTransform();
const scale = docView?.screenToViewTransform().Scale ?? 1;
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
- var foundParent = false;
+ let foundParent = false;
const getAnchor = (field: FieldResult): Element[] => {
const docField = DocCast(field);
const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id]));
if (ele?.className === 'linkBox-label') foundParent = true;
if (ele?.getBoundingClientRect().width) return [ele];
- const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+ const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width);
const annoOn = DocCast(doc.annotationOn);
if (eles.length || !annoOn) return eles;
const pareles = getAnchor(annoOn);
- foundParent = pareles.length ? true : false;
+ foundParent = !!pareles.length;
return pareles;
};
// if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
@@ -121,26 +128,38 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
if (!aid || !bid) {
- setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+ setTimeout(
+ action(() => {
+ this._forceAnimate += 0.01;
+ })
+ );
return null;
}
if (foundParent) {
setTimeout(
- action(() => (this._forceAnimate = this._forceAnimate + 0.01)),
+ action(() => {
+ this._forceAnimate += 0.01;
+ }),
1
);
}
- if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
+ if (at || bt)
+ setTimeout(
+ action(() => {
+ this._forceAnimate += 0.01;
+ })
+ ); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
- const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
+ // eslint-disable-next-line camelcase
+ const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document;
- const strokeWidth = NumCast(stroke_width, 4);
+ const strokeWidth = NumCast(strokeRawWidth, 4);
const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
@@ -151,12 +170,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
start={aid}
end={bid} //
strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
- showHead={stroke_startMarker ? true : false}
- showTail={stroke_endMarker ? true : false}
- headSize={NumCast(stroke_markerScale, 3)}
- tailSize={NumCast(stroke_markerScale, 3)}
- tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
- headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ showHead={!!strokeStartMarker}
+ showTail={!!strokeEndMarker}
+ headSize={NumCast(strokeMarkerScale, 3)}
+ tailSize={NumCast(strokeMarkerScale, 3)}
+ tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
color={highlightColor}
/>
)}
@@ -165,23 +184,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
start={aid}
end={bid} //
strokeWidth={strokeWidth}
- dashness={Number(stroke_dash) ? true : false}
- showHead={stroke_startMarker ? true : false}
- showTail={stroke_endMarker ? true : false}
- headSize={NumCast(stroke_markerScale, 3)}
- tailSize={NumCast(stroke_markerScale, 3)}
- tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
- headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ dashness={!!Number(strokeDash)}
+ showHead={!!strokeStartMarker}
+ showTail={!!strokeEndMarker}
+ headSize={NumCast(strokeMarkerScale, 3)}
+ tailSize={NumCast(strokeMarkerScale, 3)}
+ tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
color={color}
labels={
<div
id={this.DocumentView?.().DocUniqueId}
- className={'linkBox-label'}
+ className="linkBox-label"
style={{
borderRadius: '8px',
pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
fontSize,
- fontFamily /*, fontStyle: 'italic'*/,
+ fontFamily /* , fontStyle: 'italic' */,
color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
paddingLeft: 4,
paddingRight: 4,
@@ -222,16 +241,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
setTimeout(
- action(() => (this._forceAnimate = this._forceAnimate + 1)),
+ action(() => {
+ this._forceAnimate += 1;
+ }),
2
);
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this.props} //
fieldKey="link_anchor"
setHeight={emptyFunction}
- dontRegisterView={true}
+ dontRegisterView
renderDepth={this._props.renderDepth + 1}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 2a96ce458..23cb25962 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -9,6 +9,7 @@ import { StrCast } from '../../../fields/Types';
@observer
export class LinkDescriptionPopup extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: LinkDescriptionPopup;
@observable public display: boolean = false;
@observable public showDescriptions: string = 'ON';
@@ -23,6 +24,20 @@ export class LinkDescriptionPopup extends React.Component<{}> {
LinkDescriptionPopup.Instance = this;
}
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onClick, true);
+ reaction(
+ () => this.display,
+ display => {
+ display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description));
+ }
+ );
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('pointerdown', this.onClick, true);
+ }
+
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
this.description = e.currentTarget.value;
@@ -39,25 +54,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
onClick = (e: PointerEvent) => {
- if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+ if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) {
this.display = false;
this.description = '';
TaskCompletionBox.taskCompleted = false;
}
};
- componentDidMount() {
- document.addEventListener('pointerdown', this.onClick, true);
- reaction(
- () => this.display,
- display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
- );
- }
-
- componentWillUnmount() {
- document.removeEventListener('pointerdown', this.onClick, true);
- }
-
render() {
return !this.display ? null : (
<div
@@ -78,11 +81,11 @@ export class LinkDescriptionPopup extends React.Component<{}> {
onChange={e => this.descriptionChanged(e)}
/>
<div className="linkDescriptionPopup-btn">
- <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+ <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={() => this.onDismiss(false)}>
{' '}
Dismiss{' '}
</div>
- <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+ <div className="linkDescriptionPopup-btn-add" onPointerDown={() => this.onDismiss(true)}>
{' '}
Add{' '}
</div>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c9c8f9260..a9cfe6c0e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -4,7 +4,8 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { observer } from 'mobx-react';
import * as React from 'react';
import wiki from 'wikijs';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
import { DocServer } from '../../DocServer';
@@ -22,35 +23,41 @@ import { DocumentView, OpenWhere } from './DocumentView';
import { StyleProviderFuncType } from './FieldView';
import './LinkDocPreview.scss';
+interface LinkDocPreviewProps {
+ linkDoc?: Doc;
+ linkSrc?: Doc;
+ DocumentView?: () => DocumentView;
+ styleProvider?: StyleProviderFuncType;
+ location: number[];
+ hrefs?: string[];
+ showHeader?: boolean;
+ noPreview?: boolean;
+}
export class LinkInfo {
+ // eslint-disable-next-line no-use-before-define
private static _instance: Opt<LinkInfo>;
constructor() {
LinkInfo._instance = this;
makeObservable(this);
}
+ // eslint-disable-next-line no-use-before-define
@observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined;
public static get Instance() {
return LinkInfo._instance ?? new LinkInfo();
}
public static Clear() {
- runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined));
+ runInAction(() => {
+ LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined);
+ });
}
public static SetLinkInfo(info?: LinkDocPreviewProps) {
- runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+ runInAction(() => {
+ LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info);
+ });
}
}
-interface LinkDocPreviewProps {
- linkDoc?: Doc;
- linkSrc?: Doc;
- DocumentView?: () => DocumentView;
- styleProvider?: StyleProviderFuncType;
- location: number[];
- hrefs?: string[];
- showHeader?: boolean;
- noPreview?: boolean;
-}
@observer
export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
_infoRef = React.createRef<HTMLDivElement>();
@@ -68,13 +75,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
@action
init() {
- var linkTarget = this._props.linkDoc;
+ let linkTarget = this._props.linkDoc;
this._linkSrc = this._props.linkSrc;
this._linkDoc = this._props.linkDoc;
- const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
- const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
- if (link_anchor_1 && link_anchor_2) {
- linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
+ const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1);
+ const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2);
+ if (linkAnchor1 && linkAnchor2) {
+ linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1;
}
if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
@@ -110,7 +117,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
if (href.startsWith('https://en.wikipedia.org/wiki/')) {
wiki()
.page(href.replace('https://en.wikipedia.org/wiki/', ''))
- .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
+ .then(page =>
+ page.summary().then(
+ action(summary => {
+ this._toolTipText = summary.substring(0, 500);
+ })
+ )
+ );
} else {
this._toolTipText = 'url => ' + href;
}
@@ -132,7 +145,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
this._linkSrc = anchor;
const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
this._markerTargetDoc = linkTarget;
- this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
}
if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
}
@@ -193,7 +206,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
willPan: true,
zoomTime: 500,
});
- //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+ // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
}
};
@@ -248,9 +261,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
- if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
- DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+ (moveEv, down) => {
+ if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) {
+ DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY);
LinkInfo.Clear();
return true;
}
@@ -283,18 +296,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
- dontRegisterView={true}
+ dontRegisterView
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
renderDepth={0}
- suppressSetHeight={true}
+ suppressSetHeight
PanelWidth={this.width}
PanelHeight={this.height}
pointerEvents={returnNone}
focus={emptyFunction}
whenChildContentsActiveChanged={returnFalse}
- ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
+ ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
/>
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index adccc9db6..501831bca 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -60,7 +60,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
} else {
const updateFunc = async () => {
const result = await Networking.QueryYoutubeProgress(StrCast(this.Document[Id])); // We use the guid of the overwriteDoc to track file uploads.
- runInAction(() => (this.progress = result.progress));
+ runInAction(() => {
+ this.progress = result.progress;
+ });
!this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000));
};
this._timer = setTimeout(updateFunc, 1000);
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
index d54a175b2..f4ece627f 100644
--- a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -1,35 +1,44 @@
-import * as React from "react";
+import * as React from 'react';
export const slowSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62">
<defs>
- <style type="text/css">
- {`
+ <style type="text/css">
+ {`
.fil0 { fill: black; fill-rule: nonzero; }
.fil1 { fill: #FE0000; fill-rule: nonzero; }
`}
- </style>
+ </style>
</defs>
- <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/>
- <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/>
+ <path
+ className="fil0"
+ d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"
+ />
+ <path
+ className="fil1"
+ d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"
+ />
</svg>
);
export const mediumSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
- <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs>
- <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
- <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/>
- <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
- <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/>
+ <defs>
+ <style>{`.cls-1{fill:#fe0000;}`}</style>
+ </defs>
+ <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+ <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z" />
+ <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
+ <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z" />
</svg>
);
export const fastSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
- <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs>
- <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
- <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+ <defs>
+ <style>{`.cls-1{fill:#fe0000;`}</style>
+ </defs>
+ <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+ <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
</svg>
);
-
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 35153f439..cf5315da3 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -87,25 +87,24 @@ export class AnimationUtility {
@computed get currentPitch(): number {
if (!this.isStreetViewAnimation) return 50;
if (!this.terrainDisplayed) return 80;
- else {
- // const groundElevation = 0;
- const heightAboveGround = this.currentAnimationAltitude;
- const horizontalDistance = 500;
-
- let pitch;
- if (heightAboveGround >= 0) {
- pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
- } else {
- pitch = 80;
- }
- console.log(Math.max(50, Math.min(pitch, 85)));
+ // const groundElevation = 0;
+ const heightAboveGround = this.currentAnimationAltitude;
+ const horizontalDistance = 500;
- if (this.previousPitch) {
- return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
- }
- return Math.max(50, Math.min(pitch, 85));
+ let pitch;
+ if (heightAboveGround >= 0) {
+ pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
+ } else {
+ pitch = 80;
+ }
+
+ console.log(Math.max(50, Math.min(pitch, 85)));
+
+ if (this.previousPitch) {
+ return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
}
+ return Math.max(50, Math.min(pitch, 85));
}
@computed get flyInEndPitch() {
@@ -214,8 +213,8 @@ export class AnimationUtility {
currentAnimationPhase: number;
updateAnimationPhase: (newAnimationPhase: number) => void;
updateFrameId: (newFrameId: number) => void;
- }) => {
- return new Promise<void>(async resolve => {
+ }) =>
+ new Promise<void>(async resolve => {
let startTime: number | null = null;
const frame = async (currentTime: number) => {
@@ -257,7 +256,7 @@ export class AnimationUtility {
updateAnimationPhase(animationPhase);
// compute corrected camera ground position, so that he leading edge of the path is in view
- var correctedPosition = this.computeCameraPosition(
+ const correctedPosition = this.computeCameraPosition(
this.isStreetViewAnimation,
this.currentPitch,
bearing,
@@ -277,7 +276,7 @@ export class AnimationUtility {
map.setFreeCameraOptions(camera);
this.previousAltitude = this.currentAnimationAltitude;
- this.previousPitch = this.previousPitch;
+ // this.previousPitch = this.previousPitch;
// repeat!
const innerFrameId = await window.requestAnimationFrame(frame);
@@ -287,15 +286,14 @@ export class AnimationUtility {
const outerFrameId = await window.requestAnimationFrame(frame);
updateFrameId(outerFrameId);
});
- };
- public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
- return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+ public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) =>
+ new Promise<{ bearing: number; altitude: number }>(async resolve => {
let start: number | null;
- var currentAltitude;
- var currentBearing;
- var currentPitch;
+ let currentAltitude;
+ let currentBearing;
+ let currentPitch;
// the animation frame will run as many times as necessary until the duration has been reached
const frame = async (time: number) => {
@@ -319,7 +317,7 @@ export class AnimationUtility {
currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
// compute corrected camera ground position, so the start of the path is always in view
- var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+ const correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
// set the pitch and bearing of the camera
const camera = map.getFreeCameraOptions();
@@ -349,13 +347,10 @@ export class AnimationUtility {
const outerFrameId = await window.requestAnimationFrame(frame);
updateFrameId(outerFrameId);
});
- };
previousCameraPosition: { lng: number; lat: number } | null = null;
- lerp = (start: number, end: number, amt: number) => {
- return (1 - amt) * start + amt * end;
- };
+ lerp = (start: number, end: number, amt: number) => (1 - amt) * start + amt * end;
computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
const bearingInRadian = (bearing * Math.PI) / 180;
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index 7e99795b5..f176317d2 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -4,7 +4,8 @@ import { IconButton } from 'browndash-components';
import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -13,6 +14,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: DirectionsAnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -23,8 +25,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
// public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
@@ -55,7 +57,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
() => SelectionManager.Views.slice(),
- sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+ () => DirectionsAnchorMenu.Instance.fadeOut(true)
);
}
// audioDown = (e: React.PointerEvent) => {
@@ -103,8 +105,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
- <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
- <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+ <IconButton tooltip="Animate route" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
+ <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
</div>
);
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
index e4ba51316..e118c57d9 100644
--- a/src/client/views/nodes/MapBox/GeocoderControl.tsx
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -3,8 +3,6 @@
// import { ControlPosition, MarkerProps, useControl } from "react-map-gl";
// import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
-
-
// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
// mapboxAccessToken: string;
// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
@@ -31,7 +29,6 @@
// ctrl.on('results', props.onResults);
// ctrl.on('result', evt => {
// props.onResult(evt);
-
// // const {result} = evt;
// // const location =
// // result &&
@@ -49,8 +46,6 @@
// position: props.position
// }
// );
-
-
// // @ts-ignore (TS2339) private member
// if (geocoder._map) {
// if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
@@ -104,4 +99,4 @@
// onLoading: noop,
// onResults: noop,
// onError: noop
-// }; \ No newline at end of file
+// };
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 08bea5d9d..174511e1a 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
@@ -7,7 +8,8 @@ import { IReactionDisposer, ObservableMap, action, makeObservable, observable, r
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { CalendarManager } from '../../../util/CalendarManager';
@@ -23,6 +25,7 @@ type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize'
@observer
export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: MapAnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -35,8 +38,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
// public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
@@ -124,7 +127,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
() => SelectionManager.Views.slice(),
- sel => MapAnchorMenu.Instance.fadeOut(true)
+ () => MapAnchorMenu.Instance.fadeOut(true)
);
}
// audioDown = (e: React.PointerEvent) => {
@@ -147,12 +150,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartDrag(e, this._commentRef.current!);
+ moveEv => {
+ this.StartDrag(moveEv, this._commentRef.current!);
return true;
},
returnFalse,
- e => this.OnClick(e)
+ clickEv => this.OnClick(clickEv)
);
};
@@ -274,7 +277,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
HandleAddRouteClick = () => {
if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) {
- const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+ const { coordinates } = this.currentRouteInfoMap[this.selectedTransportationType];
console.log(coordinates);
console.log(this.selectedDestinationFeature);
this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
@@ -293,34 +296,30 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
- getAddToCalendarButton = (docType: string): JSX.Element => {
- return (
- <IconButton
- tooltip="Add to calendar"
- onPointerDown={() => {
- CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
- }}
- icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
- color={SettingsManager.userColor}
- />
- );
- };
+ getAddToCalendarButton = (docType: string): JSX.Element => (
+ <IconButton
+ tooltip="Add to calendar"
+ onPointerDown={() => {
+ CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+ }}
+ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ );
addToCalendarButton: JSX.Element = (
<IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
);
- getLinkNoteToDocButton = (docType: string): JSX.Element => {
- return (
- <div ref={this._commentRef}>
- <IconButton
- tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
- onPointerDown={this.notePointerDown}
- icon={<FontAwesomeIcon icon="sticky-note" />}
- color={SettingsManager.userColor}
- />
- </div>
- );
- };
+ getLinkNoteToDocButton = (docType: string): JSX.Element => (
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.notePointerDown}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
+ );
linkNoteToPinOrRoutenButton: JSX.Element = (
<div ref={this._commentRef}>
@@ -362,16 +361,14 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
);
- getDeleteButton = (type: string) => {
- return (
- <IconButton
- tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={SettingsManager.userColor}
- />
- );
- };
+ getDeleteButton = (type: string) => (
+ <IconButton
+ tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={SettingsManager.userColor}
+ />
+ );
animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
@@ -452,18 +449,17 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}}
options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)}
getOptionLabel={(feature: any) => feature.place_name}
+ // eslint-disable-next-line react/jsx-props-no-spreading
renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />}
/>
- {this.selectedDestinationFeature && (
- <>
- {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
- <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
- </div>
- )}
- </>
- )}
- <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
+ {!this.selectedDestinationFeature
+ ? null
+ : !this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
+ <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
+ </div>
+ )}
+ <button id="get-routes-button" disabled={!this.selectedDestinationFeature} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
Get routes
</button>
@@ -516,7 +512,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</div>
))}
</div>
- <div style={{ width: '100%', height: '3px', color: 'white' }}></div>
+ <div style={{ width: '100%', height: '3px', color: 'white' }} />
</div>
)}
{this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b73898f59..822485b8d 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -12,7 +12,8 @@ import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
import { MarkerEvent } from 'react-map-gl/dist/esm/types';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocumentType } from '../../../documents/DocumentTypes';
@@ -22,14 +23,14 @@ import { DragManager } from '../../../util/DragManager';
import { LinkManager } from '../../../util/LinkManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
+import { PresBox } from '../trails';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
import { MapAnchorMenu } from './MapAnchorMenu';
@@ -54,7 +55,6 @@ import { MarkerIcons } from './MarkerIcons';
const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
-
const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
type PopupInfo = {
@@ -111,13 +111,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
// this list contains pushpins and configs
- @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
- @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
- @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
- @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
- @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
- @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } //prettier-ignore
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
+ @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
+ @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
+ @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
+ @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
@computed get sidebarColor() {
return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@@ -258,7 +258,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
});
}
- }); //add to annotation list
+ }); // add to annotation list
return this.addDocument(doc, sidebarKey); // add to sidebar list
};
@@ -325,7 +325,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -389,7 +389,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
- sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ sidebarMove = (e: PointerEvent) => {
const bounds = this._ref.current!.getBoundingClientRect();
this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -401,8 +401,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
infoWidth = () => this._props.PanelWidth() / 5;
infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -653,7 +653,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
console.error(features);
if (features && features.length > 0 && features[0].properties && features[0].geometry) {
const geometry = features[0].geometry as LineString;
- const routeTitle: string = features[0].properties['routeTitle'];
+ const { routeTitle } = features[0].properties;
const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
this.deselectPinOrRoute(); // TODO: Also deselect route if selected
if (routeDoc) {
@@ -698,7 +698,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
*/
handleMapDblClick = async (e: MapLayerMouseEvent) => {
e.preventDefault();
- const lngLat: LngLat = e.lngLat;
+ const { lngLat }: LngLat = e;
const longitude: number = lngLat.lng;
const latitude: number = lngLat.lat;
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 592330ac2..5c5192372 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -1,4 +1,3 @@
-
const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox';
@@ -7,92 +6,79 @@ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3
export type TransportationType = 'driving' | 'cycling' | 'walking';
export class MapboxApiUtility {
-
static forwardGeocodeForFeatures = async (searchText: string) => {
try {
- const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
const response = await fetch(url);
const data = await response.json();
return data.features;
- } catch (error: any){
- // TODO: handle error in better way
+ } catch (error: any) {
+ // TODO: handle error in better way
return null;
}
- }
+ };
static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => {
try {
- const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
- `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + ',' + latitude.toString()) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
const response = await fetch(url);
const data = await response.json();
return data.features;
- } catch (error: any){
+ } catch (error: any) {
return null;
}
- }
+ };
static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
try {
-
const directionsPromises: Promise<any>[] = [];
const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking'];
- transportationTypes.forEach((type) => {
- directionsPromises.push(
- fetch(
- `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`
- ).then((response) => response.json())
- );
- });
+ transportationTypes.forEach(type => {
+ directionsPromises.push(fetch(`${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`).then(response => response.json()));
+ });
const results = await Promise.all(directionsPromises);
const routeInfoMap: Record<TransportationType, any> = {
- 'driving': {},
- 'cycling': {},
- 'walking': {},
+ driving: {},
+ cycling: {},
+ walking: {},
};
transportationTypes.forEach((type, index) => {
const routeData = results[index].routes[0];
if (routeData) {
- const geometry = routeData.geometry;
- const coordinates = geometry.coordinates;
-
- routeInfoMap[type] = {
- duration: this.secondsToMinutesHours(routeData.duration),
- distance: this.metersToMiles(routeData.distance),
- coordinates: coordinates,
- };
+ const { geometry } = routeData;
+ const { coordinates } = geometry;
+
+ routeInfoMap[type] = {
+ duration: this.secondsToMinutesHours(routeData.duration),
+ distance: this.metersToMiles(routeData.distance),
+ coordinates: coordinates,
+ };
}
- });
+ });
return routeInfoMap;
- // return current route info, and the temporary route
-
- } catch (error: any){
+ // return current route info, and the temporary route
+ } catch (error: any) {
return undefined;
- console.log("Error: ", error);
}
- }
+ };
private static secondsToMinutesHours = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60).toFixed(2);
- if (hours === 0){
- return `${minutes} min`
- } else {
- return `${hours} hr ${minutes} min`
+ if (hours === 0) {
+ return `${minutes} min`;
}
- }
-
- private static metersToMiles = (meters: number) => {
- return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
- }
+ return `${hours} hr ${minutes} min`;
+ };
+ private static metersToMiles = (meters: number) => `${parseFloat((meters / 1609.34).toFixed(2))} mi`;
}
// const drivingQuery = await fetch(
@@ -136,4 +122,4 @@ export class MapboxApiUtility {
// distance: this.metersToMiles(routeData.distance),
// coordinates: coordinates
// }
-// }) \ No newline at end of file
+// })
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
index a580fcaa0..087472112 100644
--- a/src/client/views/nodes/MapBox/MarkerIcons.tsx
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -17,8 +17,6 @@ import {
faHouse,
faLandmark,
faLocationDot,
- faLocationPin,
- faMapPin,
faMasksTheater,
faMugSaucer,
faPersonHiking,
@@ -65,6 +63,7 @@ export class MarkerIcons {
iconProps.color = color;
}
+ // eslint-disable-next-line react/jsx-props-no-spreading
return <FontAwesomeIcon {...iconProps} size={size} />;
}
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 3eb051dbf..fce52ef4b 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -4,28 +4,29 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { MapProvider, Map as MapboxMap } from 'react-map-gl';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight } from '../../../../fields/DocSymbols';
+import { Id } from '../../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocumentType } from '../../../documents/DocumentTypes';
import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../../DocComponent';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
+import '../MapBox/MapBox.scss';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
-import './MapBox.scss';
+import { PresBox } from '../trails';
/**
* MapBox architecture:
@@ -41,7 +42,6 @@ import './MapBox.scss';
*/
const mapboxApiKey = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg';
-const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
/**
* Consider integrating later: allows for drawing, circling, making shapes on map
@@ -87,7 +87,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
}
@computed get SidebarShown() {
- return this.layoutDoc._layout_showSidebar ? true : false;
+ return !!this.layoutDoc._layout_showSidebar;
}
@computed get sidebarWidthPercent() {
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -118,9 +118,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* @param sidebarKey
* @returns
*/
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ sidebarAddDocument = (docsIn: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
+ const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
docs.forEach(doc => {
let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
@@ -137,14 +137,18 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
}
});
}
- }); //add to annotation list
+ }); // add to annotation list
- return this.addDocument(doc, sidebarKey); // add to sidebar list
+ return this.addDocument(docs, sidebarKey); // add to sidebar list
};
removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
const docs = doc instanceof Doc ? [doc] : doc;
- this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
+ this.allAnnotations
+ .filter(anno => docs.includes(DocCast(anno.mapPin)))
+ .forEach(anno => {
+ anno.mapPin = undefined;
+ });
return this.removeDocument(doc, annotationKey, undefined);
};
@@ -164,7 +168,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
setupMoveUpEvents(
this,
e,
- (e, down, delta) =>
+ (moveEv, down, delta) =>
runInAction(() => {
const localDelta = this._props
.ScreenToLocalTransform()
@@ -204,7 +208,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -239,10 +243,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
const docView = this.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
- e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ dragComplete: dragEv => {
+ if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+ dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
+ dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
});
@@ -268,7 +272,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
- sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ sidebarMove = (e: PointerEvent) => {
const bounds = this._ref.current!.getBoundingClientRect();
this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -276,7 +280,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
return false;
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
@@ -285,8 +291,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
infoWidth = () => this._props.PanelWidth() / 5;
infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -306,11 +312,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
// center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
// });
//
- bingGeocode = (map: any, query: string) => {
- return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
- //If search manager is not defined, load the search module.
+ bingGeocode = (map: any, query: string) =>
+ new Promise<{ latitude: number; longitude: number }>((res, reject) => {
+ // If search manager is not defined, load the search module.
if (!this._bingSearchManager) {
- //Create an instance of the search manager and call the geocodeQuery function again.
+ // Create an instance of the search manager and call the geocodeQuery function again.
this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
res(this.bingGeocode(map, query));
@@ -319,11 +325,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingSearchManager.geocode({
where: query,
callback: action((r: any) => res(r.results[0].location)),
- errorCallback: (e: any) => reject(),
+ errorCallback: () => reject(),
});
}
});
- };
@observable
bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
@@ -368,7 +373,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
}
const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+ this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc));
if (!this._unmounting) {
this._bingMap.current.entities.push(newpin);
}
@@ -383,7 +388,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this.toggleSidebar();
options.didMove = true;
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
/*
* Pushpin onclick
@@ -418,7 +425,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* Map OnClick
*/
@action
- mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+ mapOnClick = (/* e: { location: { latitude: any; longitude: any } } */) => {
this._props.select(false);
this.deselectPin();
};
@@ -442,22 +449,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* Updates maptype
*/
@action
- updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
+ updateMapType = () => {
+ this.dataDoc.map_type = this._bingMap.current.getMapTypeId();
+ };
/*
* For Bing Maps
* Called by search button's onClick
* Finds the geocode of the searched contents and sets location to that location
- **/
+ * */
@action
- bingSearch = () => {
- return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
+ bingSearch = () =>
+ this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
this.dataDoc.latitude = location.latitude;
this.dataDoc.longitude = location.longitude;
this.dataDoc.map_zoom = this._bingMap.current.getZoom();
this.dataDoc.map = this.bingSearchBarContents;
});
- };
/*
* Returns doc w/ relevant info
@@ -502,7 +510,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingMap.current.entities.push(pushPin);
- this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+ this.MicrosoftMaps.Events.addHandler(pushPin, 'click', () => this.pushpinClicked(pin));
// this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
this.map_docToPinMap.set(pin, pushPin);
};
@@ -591,13 +599,15 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
};
@action
- searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+ searchbarOnEdit = (newText: string) => {
+ this.bingSearchBarContents = newText;
+ };
recolorPin = (pin: Doc, color?: string) => {
this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
this.map_docToPinMap.delete(pin);
const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+ this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(pin));
this._bingMap.current.entities.push(newpin);
this.map_docToPinMap.set(pin, newpin);
};
@@ -620,14 +630,16 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._disposers.mapLocation = reaction(
() => this.Document.map,
- mapLoc => (this.bingSearchBarContents = mapLoc),
+ mapLoc => {
+ this.bingSearchBarContents = mapLoc;
+ },
{ fireImmediately: true }
);
this._disposers.highlight = reaction(
() => this.allAnnotations.map(doc => doc[Highlight]),
() => {
const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
- allConfigPins.forEach(({ doc, pushpin }) => {
+ allConfigPins.forEach(({ pushpin }) => {
if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
this.recolorPin(pushpin);
this.map_pinHighlighted.delete(pushpin);
@@ -668,23 +680,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
setupMoveUpEvents(
e,
e,
- e => {
+ moveEv => {
if (!dragClone) {
dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
dragClone.style.position = 'absolute';
dragClone.style.zIndex = '10000';
DragManager.Root().appendChild(dragClone);
}
- dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+ dragClone.style.transform = `translate(${moveEv.clientX - 15}px, ${moveEv.clientY - 15}px)`;
return false;
},
- e => {
+ upEv => {
if (!dragClone) return;
DragManager.Root().removeChild(dragClone);
- let target = document.elementFromPoint(e.x, e.y);
+ let target = document.elementFromPoint(upEv.x, upEv.y);
while (target) {
if (target === this._ref.current) {
- const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
+ const cpt = this.ScreenToLocalBoxXf().transformPoint(upEv.clientX, upEv.clientY);
const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -694,7 +706,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
target = target.parentElement;
}
},
- e => {
+ () => {
const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
if (this.bingSearchBarContents) {
this.bingSearch().then(createPin);
@@ -720,12 +732,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
MapBoxContainer._rerenderDelay = 0;
}
this._rerenderTimeout = undefined;
+ // eslint-disable-next-line operator-assignment
this.Document[DocCss] = this.Document[DocCss] + 1;
}), MapBoxContainer._rerenderDelay);
return null;
}
- const renderAnnotations = (childFilters?: () => string[]) => null;
return (
<div className="mapBox" ref={this._ref}>
<div
@@ -735,15 +747,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
e.button === 0 && !e.ctrlKey && e.stopPropagation();
}}
style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
- <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.IsDragging ? null : renderAnnotations()}
-
<div className="mapBox-searchbar">
<EditableText
// editing
setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
- onEnter={e => this.bingSearch()}
+ onEnter={() => this.bingSearch()}
placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
textAlign="center"
/>
@@ -752,18 +760,19 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
<path
fill="currentColor"
- d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+ d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"
+ />
</svg>
}
onClick={this.bingSearch}
type={Type.TERT}
/>
<div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
- <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+ <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size="lg" icon="bullseye" />} />
</div>
</div>
<MapProvider>
- <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} />
+ <MapboxMap id="mabox-map" mapStyle="mapbox://styles/mapbox/streets-v9" mapboxAccessToken={mapboxApiKey} />
</MapProvider>
{/*
@@ -780,9 +789,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
? null
: this.allAnnotations
.filter(anno => !anno.layout_unrendered)
- .map((pushpin, i) => (
+ .map(pushpin => (
<DocumentView
- key={i}
+ key={pushpin[Id]}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
renderDepth={this._props.renderDepth + 1}
Document={pushpin}
@@ -821,12 +831,13 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey={this.fieldKey}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
showSidebar={this.SidebarShown}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 0f5e25a0c..7bca1230f 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -236,7 +236,7 @@
//pointer-events: none;
.pdfViewerDash-text {
.textLayer {
- display: none;
+ // display: none; // this makes search highlights not show up
span {
user-select: none;
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1274220b6..b03b90418 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as React from 'react';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -12,7 +15,7 @@ import { ComputedField } from '../../../fields/ScriptField';
import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -23,16 +26,17 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionStackingView } from '../collections/CollectionStackingView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { Colors } from '../global/globalEnums';
-import { CreateImage } from '../nodes/WebBoxRenderer';
+// eslint-disable-next-line import/extensions
+import { CreateImage } from './WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
import { DocumentView, OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import { ImageBox } from './ImageBox';
import './PDFBox.scss';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -66,8 +70,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
!this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
if (this.pdfUrl) {
- if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
- else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
+ if (PDFBox.pdfcache.get(this.pdfUrl.url.href))
+ runInAction(() => {
+ this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href);
+ });
+ else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href))
+ PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(
+ action((pdf: any) => {
+ this._pdf = pdf;
+ })
+ );
}
}
@@ -85,7 +97,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (oldDiv instanceof HTMLCanvasElement) {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image sourcez
+ img.src = canvas.toDataURL(); // image sourcez
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -96,7 +108,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
const regionData = region[DocData];
regionData.lockedPosition = true;
@@ -111,11 +123,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.replaceCanvases(docViewContent, newDiv);
const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
- const anchx = NumCast(cropping.x);
- const anchy = NumCast(cropping.y);
+ // const anchx = NumCast(cropping.x);
+ // const anchy = NumCast(cropping.y);
const anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
- const viewScale = 1;
+ // const viewScale = 1;
cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
@@ -128,9 +140,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
croppingProto.type = DocumentType.IMG;
croppingProto.layout = ImageBox.LayoutString('data');
- croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
@@ -146,8 +158,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
(NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
4
)
- .then((data_url: any) => {
- Utils.convertDataUri(data_url, region[Id]).then(returnedfilename =>
+ .then((dataUrl: any) => {
+ ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename =>
setTimeout(
action(() => {
croppingProto.data = new ImageField(returnedfilename);
@@ -156,7 +168,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)
);
})
- .catch(function (error: any) {
+ .catch((error: any) => {
console.error('oops, something went wrong!', error);
});
@@ -182,8 +194,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
(iconFile: string, nativeWidth: number, nativeHeight: number) => {
setTimeout(() => {
this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc['icon_nativeWidth'] = nativeWidth;
- this.dataDoc['icon_nativeHeight'] = nativeHeight;
+ this.dataDoc.icon_nativeWidth = nativeWidth;
+ this.dataDoc.icon_nativeHeight = nativeHeight;
}, 500);
}
);
@@ -214,12 +226,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
- sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
- if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+ sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => {
+ const docs = docIn instanceof Doc ? [docIn] : docIn;
+ if (docs.some(doc => DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc)) && !this.SidebarShown) {
this.toggleSidebar(false);
return true;
}
- return this._props.addDocTab(doc, where);
+ return this._props.addDocTab(docs, where);
};
focus = (anchor: Doc, options: FocusViewOptions) => {
this._initialScrollTarget = anchor;
@@ -231,18 +244,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
options.didMove = true;
this.toggleSidebar(false);
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
if (this._pdfViewer?.selectionContent()) {
ele = document.createElement('div');
ele.append(this._pdfViewer.selectionContent()!);
}
const docAnchor = () =>
Docs.Create.ConfigDocument({
- title: StrCast(this.Document.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
+ title: StrCast(this.Document.title + '@' + (NumCast(this.layoutDoc._layout_scrollTop) ?? 0).toFixed(0)),
annotationOn: this.Document,
});
const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
@@ -285,7 +300,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this._props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
return true;
};
- public gotoPage = (p: number) => (this.Document._layout_curPage = p);
+ public gotoPage = (p: number) => {
+ this.Document._layout_curPage = p;
+ };
@undoBatch
onKeyDown = action((e: KeyboardEvent) => {
@@ -297,6 +314,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
case 'PageUp':
processed = this.backPage();
break;
+ default:
}
if (processed) {
e.stopImmediatePropagation();
@@ -312,7 +330,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._initialScrollTarget = undefined;
}
};
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._searchString = e.currentTarget.value;
+ };
// adding external documents; to sidebar key
// if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"_annotation")
@@ -326,7 +346,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ (moveEv, down, delta) => {
const localDelta = this._props
.ScreenToLocalTransform()
.scale(this._props.NativeDimScaling?.() || 1)
@@ -341,7 +361,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
return false;
},
- (e, movement, isClick) => !isClick && batch.end(),
+ (clickEv, movement, isClick) => !isClick && batch.end(),
() => {
onButton && this.toggleSidebar();
batch.end();
@@ -368,11 +388,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
settingsPanel() {
const pageBtns = (
<>
- <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
- <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" />
+ <button type="button" className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-left" size="sm" />
</button>
- <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
- <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" />
+ <button type="button" className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-right" size="sm" />
</button>
</>
);
@@ -385,7 +405,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
onPointerDown={e => e.stopPropagation()}
style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="pdfBox-overlayButton" title={searchTitle} />
+ <button type="button" className="pdfBox-overlayButton" title={searchTitle} />
<input
className="pdfBox-searchBar"
placeholder="Search"
@@ -396,17 +416,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
}}
/>
- <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <button type="button" className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
- <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
- <FontAwesomeIcon icon={'arrow-up'} size="lg" />
+ <button type="button" className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
+ <FontAwesomeIcon icon="arrow-up" size="lg" />
</button>
- <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
- <FontAwesomeIcon icon={'arrow-down'} size="lg" />
+ <button type="button" className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
+ <FontAwesomeIcon icon="arrow-down" size="lg" />
</button>
</div>
<button
+ type="button"
className="pdfBox-overlayButton"
title={searchTitle}
onClick={action(() => {
@@ -423,9 +444,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<input
value={curPage}
style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }}
- onChange={e => (this.Document._layout_curPage = Number(e.currentTarget.value))}
+ onChange={e => {
+ this.Document._layout_curPage = Number(e.currentTarget.value);
+ }}
onKeyDown={e => e.stopPropagation()}
- onClick={action(() => (this._pageControls = !this._pageControls))}
+ onClick={action(() => {
+ this._pageControls = !this._pageControls;
+ })}
/>
{this._pageControls ? pageBtns : null}
</div>
@@ -440,18 +465,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
};
@undoBatch
- toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
- specificContextMenu = (e: React.MouseEvent): void => {
+ toggleSidebarType = () => {
+ this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform;
+ };
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
!Doc.noviceMode && optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
!Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
- //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+ // optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
- helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+ helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && ClientUtils.CopyText(ClientUtils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
!help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
};
@@ -469,7 +496,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@observable _showSidebar = false;
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._show_sidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._show_sidebar);
}
@computed get sidebarHandle() {
return (
@@ -483,7 +510,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -514,6 +541,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
@@ -529,6 +557,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
@@ -539,7 +568,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
yPadding={0}
viewField={this.SidebarKey}
isAnnotationOverlay={false}
- originTopLeft={true}
+ originTopLeft
isAnyChildContentActive={this.isAnyChildContentActive}
select={emptyFunction}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -548,7 +577,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
renderDepth={this._props.renderDepth + 1}
- noSidebar={true}
+ noSidebar
fieldKey={this.SidebarKey}
/>
</div>
@@ -582,6 +611,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
top: 0,
}}>
<PDFViewer
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
pdfBox={this}
sidebarAddDoc={this.sidebarAddDocument}
@@ -614,10 +644,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const pdfView = !this._pdf ? null : this.renderPdfView;
const href = this.pdfUrl?.url.href;
if (!pdfView && href) {
- if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
+ if (PDFBox.pdfcache.get(href))
+ setTimeout(
+ action(() => {
+ this._pdf = PDFBox.pdfcache.get(href);
+ })
+ );
else {
if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
- PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
+ PDFBox.pdfpromise.get(href)?.then(
+ action((pdf: any) => {
+ PDFBox.pdfcache.set(href, (this._pdf = pdf));
+ })
+ );
}
}
return pdfView ?? this.renderTitleBox;
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 16450c359..48da4937a 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -6,22 +6,57 @@ import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
@observer
export class RadialMenu extends React.Component {
+ // eslint-disable-next-line no-use-before-define
static Instance: RadialMenu;
static readonly buffer = 20;
+ @observable private _mouseX: number = -1;
+ @observable private _mouseY: number = -1;
+ @observable private _shouldDisplay: boolean = false;
+ @observable private _mouseDown: boolean = false;
+ @observable private _closest: number = -1;
+ @observable private _pageX: number = 0;
+ @observable private _pageY: number = 0;
+ @observable _display: boolean = false;
+ @observable private _yRelativeToTop: boolean = true;
+ @observable private _items: Array<RadialMenuProps> = [];
+ private _reactionDisposer?: IReactionDisposer;
+
constructor(props: any) {
super(props);
makeObservable(this);
RadialMenu.Instance = this;
}
- @observable private _mouseX: number = -1;
- @observable private _mouseY: number = -1;
- @observable private _shouldDisplay: boolean = false;
- @observable private _mouseDown: boolean = false;
- private _reactionDisposer?: IReactionDisposer;
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onPointerDown);
+ document.addEventListener('pointerup', this.onPointerUp);
+ this.previewcircle();
+ this._reactionDisposer = reaction(
+ () => this._shouldDisplay,
+ () =>
+ this._shouldDisplay &&
+ !this._mouseDown &&
+ runInAction(() => {
+ this._display = true;
+ })
+ );
+ }
- public used: boolean = false;
+ componentDidUpdate() {
+ this.previewcircle();
+ }
+ componentWillUnmount() {
+ document.removeEventListener('pointerdown', this.onPointerDown);
+
+ document.removeEventListener('pointerup', this.onPointerUp);
+ this._reactionDisposer && this._reactionDisposer();
+ }
+
+ @computed get menuItems() {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+ }
catchTouch = (te: React.TouchEvent) => {
te.stopPropagation();
@@ -33,13 +68,9 @@ export class RadialMenu extends React.Component {
this._mouseDown = true;
this._mouseX = e.clientX;
this._mouseY = e.clientY;
- this.used = false;
document.addEventListener('pointermove', this.onPointerMove);
};
- @observable
- private _closest: number = -1;
-
@action
onPointerMove = (e: PointerEvent) => {
const curX = e.clientX;
@@ -65,7 +96,6 @@ export class RadialMenu extends React.Component {
};
@action
onPointerUp = (e: PointerEvent) => {
- this.used = true;
this._mouseDown = false;
const curX = e.clientX;
const curY = e.clientY;
@@ -78,86 +108,6 @@ export class RadialMenu extends React.Component {
this._items[this._closest].event();
}
};
- componentWillUnmount() {
- document.removeEventListener('pointerdown', this.onPointerDown);
-
- document.removeEventListener('pointerup', this.onPointerUp);
- this._reactionDisposer && this._reactionDisposer();
- }
-
- @action
- componentDidMount() {
- document.addEventListener('pointerdown', this.onPointerDown);
- document.addEventListener('pointerup', this.onPointerUp);
- this.previewcircle();
- this._reactionDisposer = reaction(
- () => this._shouldDisplay,
- () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true))
- );
- }
-
- componentDidUpdate = () => {
- this.previewcircle();
- };
-
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable _display: boolean = false;
- @observable private _yRelativeToTop: boolean = true;
-
- @observable private _width: number = 0;
- @observable private _height: number = 0;
-
- getItems() {
- return this._items;
- }
-
- @action
- addItem(item: RadialMenuProps) {
- if (this._items.indexOf(item) === -1) {
- this._items.push(item);
- }
- }
-
- @observable
- private _items: Array<RadialMenuProps> = [];
-
- @action
- displayMenu = (x: number, y: number) => {
- //maxX and maxY will change if the UI/font size changes, but will work for any amount
- //of items added to the menu
- this._mouseX = x;
- this._mouseY = y;
- this._shouldDisplay = true;
- };
- // @computed
- // get pageX() {
- // const x = this._pageX;
- // if (x < 0) {
- // return 0;
- // }
- // const width = this._width;
- // if (x + width > window.innerWidth - RadialMenu.buffer) {
- // return window.innerWidth - RadialMenu.buffer - width;
- // }
- // return x;
- // }
- // @computed
- // get pageY() {
- // const y = this._pageY;
- // if (y < 0) {
- // return 0;
- // }
- // const height = this._height;
- // if (y + height > window.innerHeight - RadialMenu.buffer) {
- // return window.innerHeight - RadialMenu.buffer - height;
- // }
- // return y;
- // }
-
- @computed get menuItems() {
- return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
- }
@action
closeMenu = () => {
@@ -167,14 +117,6 @@ export class RadialMenu extends React.Component {
};
@action
- openMenu = (x: number, y: number) => {
- this._pageX = x;
- this._pageY = y;
- this._shouldDisplay;
- this._display = true;
- };
-
- @action
clearItems() {
this._items = [];
}
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index 91dc37d34..6f10e7b65 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from 'mobx-react';
@@ -53,6 +54,7 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
case 2:
color = 'lightgray';
break;
+ default:
}
if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
color = '#c2c2c5';
@@ -80,7 +82,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
const degrees = 360 * avg;
const x = 100 * Math.cos((degrees * Math.PI) / 180);
- const y = -125 * Math.sin((degrees * Math.PI) / 180);
return x;
}
@@ -91,7 +92,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
this.props.max ? (circlemax = this.props.max) : null;
const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
const degrees = 360 * avg;
- const x = 125 * Math.cos((degrees * Math.PI) / 180);
const y = -100 * Math.sin((degrees * Math.PI) / 180);
return y;
}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
index 1bb2b7c84..62798bc2f 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -1,31 +1,33 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/require-default-props */
import * as React from 'react';
-import { useEffect, useState, useCallback, useRef } from "react"
-import "./ProgressBar.scss"
+import { useEffect, useState, useRef } from 'react';
+import './ProgressBar.scss';
import { MediaSegment } from './RecordingView';
interface ProgressBarProps {
- videos: MediaSegment[],
- setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
- orderVideos: boolean,
- progress: number,
- recording: boolean,
- doUndo: boolean,
- setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>,
+ videos: MediaSegment[];
+ setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>;
+ orderVideos: boolean;
+ progress: number;
+ recording: boolean;
+ doUndo: boolean;
+ setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>;
}
interface SegmentBox {
- endTime: number,
- startTime: number,
- order: number,
+ endTime: number;
+ startTime: number;
+ order: number;
}
interface CurrentHover {
- index: number,
- minX: number,
- maxX: number
+ index: number;
+ minX: number;
+ maxX: number;
}
export function ProgressBar(props: ProgressBarProps) {
- const progressBarRef = useRef<HTMLDivElement | null>(null)
+ const progressBarRef = useRef<HTMLDivElement | null>(null);
// the actual list of JSX elements rendered as segments
const [segments, setSegments] = useState<JSX.Element[]>([]);
@@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) {
// update the canUndo props based on undo stack
useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]);
- // useEffect for undo - brings back the most recently deleted segment
- useEffect(() => handleUndo(), [props.doUndo])
const handleUndo = () => {
// get the last element from the undo if it exists
if (undoStack.length === 0) return;
@@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) {
// update the removed time and place element back into ordered
setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
setOrdered(prevOrdered => [...prevOrdered, last]);
- }
+ };
+ // useEffect for undo - brings back the most recently deleted segment
+ useEffect(() => handleUndo(), [props.doUndo]);
// useEffect for recording changes - changes style to disabled and adds the "expanding-segment"
useEffect(() => {
// get segments segment's html using it's id -> make them appeared disabled (or enabled)
- segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+ segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
if (props.recording)
- setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]);
- }, [props.recording])
-
+ setSegments(prevSegments => [
+ ...prevSegments,
+ <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}>
+ {props.videos.length + 1}
+ </div>,
+ ]);
+ }, [props.recording]);
// useEffect that updates the segmentsJSX, which is rendered
// only updated when ordered is updated or if the user is dragging around a segment
useEffect(() => {
const totalTime = props.progress * 1000 - totalRemovedTime;
- const segmentsJSX = ordered.map((seg, i) =>
- <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+ const segmentsJSX = ordered.map((seg, i) => (
+ <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>
+ {seg.order + 1}
+ </div>
+ ));
- setSegments(segmentsJSX)
+ setSegments(segmentsJSX);
}, [dragged, ordered]);
// useEffect for dragged - update the cursor to be grabbing while grabbing
@@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) {
// to imporve performance, only want to update the CSS width, not re-render the whole JSXList
useEffect(() => {
- if (!props.recording) return
+ if (!props.recording) return;
const totalTime = props.progress * 1000 - totalRemovedTime;
let remainingTime = totalTime;
segments.forEach((seg, i) => {
// for the last segment, we need to set that directly
if (i === segments.length - 1) return;
// update remaining time
- remainingTime -= (ordered[i].endTime - ordered[i].startTime);
+ remainingTime -= ordered[i].endTime - ordered[i].startTime;
// update the width for this segment
const htmlId = seg.props.id;
@@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) {
// update the width of the expanding segment using the remaining time
const segExapandHtml = document.getElementById('segment-expanding');
- if (segExapandHtml)
- segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
+ if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
}, [props.progress]);
// useEffect for props.videos - update the ordered array when a new video is added
@@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) {
// in this case, a new video is added -> push it onto ordered
if (order >= ordered.length) {
const { endTime, startTime } = props.videos.lastElement();
- setOrdered(prevOrdered => {
- return [...prevOrdered, { endTime, startTime, order }];
- });
+ setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]);
}
// in this case, a video is removed
@@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) {
}, [props.videos]);
// useEffect for props.orderVideos - matched the order array with the videos array before the export
- useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]);
+ useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]);
// useEffect for removed - handles logic for removing a segment
useEffect(() => {
@@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) {
// returns the new currentHover based on the new index
const updateCurrentHover = (segId: number): CurrentHover | null => {
// get the segId of the segment that will become the new bounding area
- const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
- if (rect == null) return null
+ const rect = progressBarRef.current?.children[segId].getBoundingClientRect();
+ if (rect == null) return null;
return {
index: segId,
minX: rect.x,
maxX: rect.x + rect.width,
- }
- }
+ };
+ };
+
+ const swapSegments = (oldIndex: number, newIndex: number) => {
+ if (newIndex == null) return;
+ setOrdered(prevOrdered => {
+ const temp = { ...prevOrdered[oldIndex] };
+ prevOrdered[oldIndex] = prevOrdered[newIndex];
+ prevOrdered[newIndex] = temp;
+ return prevOrdered;
+ });
+ // update visually where the segment is hovering over
+ setDragged(newIndex);
+ };
+
+ // functions for the floating segment that tracks the cursor while grabbing it
+ const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+ dot.classList.add('segment-selected');
+ dot.style.transitionDuration = '0s';
+ dot.style.position = 'absolute';
+ dot.style.zIndex = '999';
+ dot.style.width = `${rect.width}px`;
+ dot.style.height = `${rect.height}px`;
+ dot.style.left = `${rect.x}px`;
+ dot.style.top = `${rect.y}px`;
+ dot.draggable = false;
+ document.body.append(dot);
+ };
+ const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+ // event.stopPropagation()
+ const { width, height } = dot.getBoundingClientRect();
+ dot.style.left = `${event.clientX - width / 2}px`;
+ dot.style.top = `${event.clientY - height / 2}px`;
+ };
// pointerdown event for the progress bar
- const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- // don't move the videobox element
- e.stopPropagation();
+ const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ // don't move the videobox element
+ e.stopPropagation();
// if recording, do nothing
- if (props.recording) return;
+ if (props.recording) return;
// get the segment the user clicked on to be dragged
- const clickedSegment = e.target as HTMLDivElement & EventTarget
+ const clickedSegment = e.target as HTMLDivElement & EventTarget;
// get the profess bar ro add event listeners
// don't do anything if null
- const progressBar = progressBarRef.current
- if (progressBar == null || clickedSegment.id === progressBar.id) return
+ const progressBar = progressBarRef.current;
+ if (progressBar == null || clickedSegment.id === progressBar.id) return;
// if holding shift key, let's remove that segment
if (e.shiftKey) {
const segId = parseInt(clickedSegment.id.split('-')[1]);
setRemoved(segId);
- return
+ return;
}
// if holding ctrl key and click, let's undo that segment #hiddenfeature lol
@@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) {
// if we're here, the user is dragging a segment around
// let the progress bar capture all the pointer events until the user releases (pointerUp)
const ptrId = e.pointerId;
- progressBar.setPointerCapture(ptrId)
+ progressBar.setPointerCapture(ptrId);
- const rect = clickedSegment.getBoundingClientRect()
- // id for segment is like 'segment-1' or 'segment-10',
+ const rect = clickedSegment.getBoundingClientRect();
+ // id for segment is like 'segment-1' or 'segment-10',
// so this works to get the id
- const segId = parseInt(clickedSegment.id.split('-')[1])
+ const segId = parseInt(clickedSegment.id.split('-')[1]);
// set the selected segment to be the one dragged
- setDragged(segId)
+ setDragged(segId);
- // this is the logic for storing the lower X bound and upper X bound to know
+ // this is the logic for storing the lower X bound and upper X bound to know
// whether a swap is needed between two segments
let currentHover: CurrentHover = {
index: segId,
minX: rect.x,
maxX: rect.x + rect.width,
- }
+ };
// create the floating segment that tracks the cursor
- const detchedSegment = document.createElement("div")
- initDeatchSegment(detchedSegment, rect);
+ const detchedSegment = document.createElement('div');
+ initDetachSegment(detchedSegment, rect);
const updateSegmentOrder = (event: PointerEvent): void => {
event.stopPropagation();
@@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) {
// this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
if (!progressBar.hasPointerCapture(ptrId)) {
+ // eslint-disable-next-line no-use-before-define
placeSegmentandCleanup();
return;
}
@@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) {
const curX = event.clientX;
// handle the left bound
if (curX < currentHover.minX && currentHover.index > 0) {
- swapSegments(currentHover.index, currentHover.index - 1)
- currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover
+ swapSegments(currentHover.index, currentHover.index - 1);
+ currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover;
}
// handle the right bound
else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) {
- swapSegments(currentHover.index, currentHover.index + 1)
- currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover
+ swapSegments(currentHover.index, currentHover.index + 1);
+ currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover;
}
- }
+ };
// handles when the user is done dragging the segment (pointerUp)
const placeSegmentandCleanup = (event?: PointerEvent): void => {
event?.stopPropagation();
event?.preventDefault();
// if they put the segment outside of the bounds, remove it
- if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight))
- setRemoved(currentHover.index);
-
+ if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index);
+
// remove the update event listener for pointermove
progressBar.removeEventListener('pointermove', updateSegmentOrder);
// remove the floating segment from the DOM
@@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) {
// dragged is -1 is equiv to nothing being dragged, so the normal state
// so this will place the segment in it's location and update the segment bar
setDragged(-1);
- }
+ };
// event listeners that allow the user to drag and release the floating segment
progressBar.addEventListener('pointermove', updateSegmentOrder);
progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true });
- }
-
- const swapSegments = (oldIndex: number, newIndex: number) => {
- if (newIndex == null) return;
- setOrdered(prevOrdered => {
- const temp = { ...prevOrdered[oldIndex] }
- prevOrdered[oldIndex] = prevOrdered[newIndex]
- prevOrdered[newIndex] = temp
- return prevOrdered
- });
- // update visually where the segment is hovering over
- setDragged(newIndex);
- }
-
- // functions for the floating segment that tracks the cursor while grabbing it
- const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
- dot.classList.add("segment-selected");
- dot.style.transitionDuration = '0s';
- dot.style.position = 'absolute';
- dot.style.zIndex = '999';
- dot.style.width = `${rect.width}px`;
- dot.style.height = `${rect.height}px`;
- dot.style.left = `${rect.x}px`;
- dot.style.top = `${rect.y}px`;
- dot.draggable = false;
- document.body.append(dot);
- }
- const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
- // event.stopPropagation()
- const { width, height } = dot.getBoundingClientRect();
- dot.style.left = `${event.clientX - width / 2}px`;
- dot.style.top = `${event.clientY - height / 2}px`;
- }
-
+ };
return (
<div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
{segments}
</div>
- )
-} \ No newline at end of file
+ );
+}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 1f976f926..2bc749e1b 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -11,17 +11,18 @@ import { Upload } from '../../../../server/SharedMediaTypes';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { Presentation } from '../../../util/TrackMovements';
import { undoBatch } from '../../../util/UndoManager';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
-import { media_state } from '../AudioBox';
+import { mediaState } from '../AudioBox';
import { FieldView, FieldViewProps } from '../FieldView';
import { VideoBox } from '../VideoBox';
import { RecordingView } from './RecordingView';
import { DocData } from '../../../../fields/DocSymbols';
+import { dropActionType } from '../../../util/DropActionTypes';
@observer
export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -43,16 +44,11 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@observable result: Upload.AccessPathInfo | undefined = undefined;
- @observable videoDuration: number | undefined = undefined;
-
- @action
- setVideoDuration = (duration: number) => (this.videoDuration = duration);
@action
setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
this.result = info;
this.dataDoc.type = DocumentType.VID;
- this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration;
this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client);
@@ -68,17 +64,17 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static WorkspaceStopRecording() {
const remDoc = RecordingBox.screengrabber?.Document;
if (remDoc) {
- //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
+ // if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
RecordingBox.screengrabber?.Pause?.();
setTimeout(() => {
RecordingBox.screengrabber?.Finish?.();
- remDoc.overlayX = 70; //was 100
+ remDoc.overlayX = 70; // was 100
remDoc.overlayY = 590;
RecordingBox.screengrabber = undefined;
}, 100);
- //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
+ // could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000);
- Doc.UserDoc().workspaceRecordingState = media_state.Paused;
+ Doc.UserDoc().workspaceRecordingState = mediaState.Paused;
Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
}
}
@@ -102,15 +98,15 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
_width: 205,
_height: 115,
});
- screengrabber.overlayX = 70; //was -400
- screengrabber.overlayY = 590; //was 0
+ screengrabber.overlayX = 70; // was -400
+ screengrabber.overlayY = 590; // was 0
screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
- Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
+ Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
RecordingBox.screengrabber.Record?.();
});
- Doc.UserDoc().workspaceRecordingState = media_state.Recording;
+ Doc.UserDoc().workspaceRecordingState = mediaState.Recording;
}
/**
@@ -136,7 +132,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@undoBatch
public static addRecToWorkspace(value: RecordingBox) {
- let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
+ const ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
(ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
Doc.RemFromMyOverlay(value.Document);
@@ -150,7 +146,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (docView?.ComponentView instanceof VideoBox) {
docView.ComponentView.Play();
}
- Doc.UserDoc().workspaceReplayingState = media_state.Playing;
+ Doc.UserDoc().workspaceReplayingState = mediaState.Playing;
}
public static pauseWorkspaceReplaying(doc: Doc) {
@@ -159,7 +155,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (videoBox) {
videoBox.Pause();
}
- Doc.UserDoc().workspaceReplayingState = media_state.Paused;
+ Doc.UserDoc().workspaceReplayingState = mediaState.Paused;
}
public static stopWorkspaceReplaying(value: Doc) {
@@ -191,63 +187,70 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
return (
<div className="recordingBox" style={{ width: '100%' }} ref={this._ref}>
- {!this.result && (
- <RecordingView
- forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
- getControls={this.getControls}
- setResult={this.setResult}
- setDuration={this.setVideoDuration}
- id={DocCast(this.Document.proto)?.[Id] || ''}
- />
- )}
+ {!this.result && <RecordingView forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} getControls={this.getControls} setResult={this.setResult} id={DocCast(this.Document.proto)?.[Id] || ''} />}
</div>
);
}
+ // eslint-disable-next-line no-use-before-define
static screengrabber: RecordingBox | undefined;
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function stopWorkspaceRecording() {
RecordingBox.WorkspaceStopRecording();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function stopWorkspaceReplaying(value: Doc) {
RecordingBox.stopWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function removeWorkspaceReplaying(value: Doc) {
RecordingBox.removeWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getCurrentRecording() {
return Doc.UserDoc().currentRecording;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getWorkspaceRecordings() {
return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function isWorkspaceRecording() {
- return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
+ return Doc.UserDoc().workspaceRecordingState === mediaState.Recording;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function isWorkspaceReplaying() {
return Doc.UserDoc().workspaceReplayingState;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
if (typeof value === 'string') RecordingBox.WorkspaceStartRecording(value);
else RecordingBox.replayWorkspace(value);
+ return undefined;
});
-ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc) {
RecordingBox.pauseWorkspaceReplaying(value);
});
-ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc) {
RecordingBox.resumeWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
if (DocCast(value.doc)) {
DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], dropActionType.embed), value.e.clientX, value.e.clientY);
value.e.preventDefault();
return true;
}
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renderDropdown() {
if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
return true;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index f7ed82643..b8451fe60 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { IconContext } from 'react-icons';
import { FaCheckCircle } from 'react-icons/fa';
import { MdBackspace } from 'react-icons/md';
import { Upload } from '../../../../server/SharedMediaTypes';
-import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Networking } from '../../../Network';
import { Presentation, TrackMovements } from '../../../util/TrackMovements';
import { ProgressBar } from './ProgressBar';
@@ -19,19 +22,18 @@ export interface MediaSegment {
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;
+const iconVals = { color: '#cc1c08', className: 'video-edit-buttons' };
export function RecordingView(props: IRecordingViewProps) {
const [recording, setRecording] = useState(false);
const recordingTimerRef = useRef<number>(0);
const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
- const [playing, setPlaying] = useState(false);
const [progress, setProgress] = useState(0);
// acts as a "refresh state" to tell progressBar when to undo
@@ -62,7 +64,7 @@ export function RecordingView(props: IRecordingViewProps) {
useEffect(() => {
if (finished) {
// make the total presentation that'll match the concatted video
- let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+ const 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 () => {
@@ -100,16 +102,16 @@ export function RecordingView(props: IRecordingViewProps) {
return () => clearInterval(interval);
}, [recording]);
+ const setVideoProgressHelper = (curProgrss: number) => {
+ const newProgress = (curProgrss / MAXTIME) * 100;
+ setProgress(newProgress);
+ };
+
useEffect(() => {
setVideoProgressHelper(recordingTimer);
recordingTimerRef.current = recordingTimer;
}, [recordingTimer]);
- const setVideoProgressHelper = (progress: number) => {
- const newProgress = (progress / MAXTIME) * 100;
- setProgress(newProgress);
- };
-
const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
@@ -131,7 +133,7 @@ export function RecordingView(props: IRecordingViewProps) {
if (event.data.size > 0) videoChunks.push(event.data);
};
- videoRecorder.current.onstart = (event: any) => {
+ videoRecorder.current.onstart = () => {
setRecording(true);
// start the recording api when the video recorder starts
(trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
@@ -149,7 +151,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 || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+ setVideos(theVideos => [...theVideos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
}
// reset the temporary chunks
@@ -186,7 +188,7 @@ export function RecordingView(props: IRecordingViewProps) {
e,
returnTrue,
returnFalse,
- e => {
+ () => {
// start recording if not already recording
if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record();
@@ -202,14 +204,8 @@ export function RecordingView(props: IRecordingViewProps) {
setDoUndo(prev => !prev);
};
- const handleOnTimeUpdate = () => {
- playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
- };
-
const millisecondToMinuteSecond = (milliseconds: number) => {
- const toTwoDigit = (digit: number) => {
- return String(digit).length == 1 ? '0' + digit : digit;
- };
+ const toTwoDigit = (digit: number) => (String(digit).length === 1 ? '0' + digit : digit);
const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
@@ -219,10 +215,11 @@ export function RecordingView(props: IRecordingViewProps) {
props.getControls(record, pause, finish);
}, []);
+ const iconUndoVals = React.useMemo(() => ({ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }), []);
return (
<div className="recording-container">
<div className="video-wrapper">
- <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} />
+ <video id={`video-${props.id}`} autoPlay muted ref={videoElementRef} />
<div className="recording-sign">
<span className="dot" />
<p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
@@ -246,10 +243,10 @@ export function RecordingView(props: IRecordingViewProps) {
{!recording &&
(videos.length > 0 ? (
<div className="options-wrapper video-edit-wrapper">
- <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}>
+ <IconContext.Provider value={iconUndoVals}>
<MdBackspace onPointerDown={undoPrevious} />
</IconContext.Provider>
- <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
+ <IconContext.Provider value={iconVals}>
<FaCheckCircle
onPointerDown={e => {
e.stopPropagation();
@@ -268,7 +265,7 @@ export function RecordingView(props: IRecordingViewProps) {
setTrackScreen(e.target.checked);
}}
/>
- <span className="checkmark"></span>
+ <span className="checkmark" />
Track Screen
</label>
</div>
diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts
index ff21eaed6..e4f9b5e55 100644
--- a/src/client/views/nodes/RecordingBox/index.ts
+++ b/src/client/views/nodes/RecordingBox/index.ts
@@ -1,2 +1,2 @@
-export * from './RecordingView'
-export * from './RecordingBox' \ No newline at end of file
+export * from './RecordingView';
+export * from './RecordingBox';
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1e3933ac3..882f6ba9e 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,33 +1,35 @@
+/* eslint-disable jsx-a11y/media-has-caption */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as React from 'react';
// import { Canvas } from '@react-three/fiber';
import { computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
+import { returnFalse, returnOne, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DocCast, NumCast } from '../../../fields/Types';
import { AudioField, VideoField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
import { CaptureManager } from '../../util/CaptureManager';
import { SettingsManager } from '../../util/SettingsManager';
import { TrackMovements } from '../../util/TrackMovements';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { media_state } from './AudioBox';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { mediaState } from './AudioBox';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import './ScreenshotBox.scss';
import { VideoBox } from './VideoBox';
-import { DocData } from '../../../fields/DocSymbols';
declare class MediaRecorder {
constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -162,7 +164,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const subitems = [{ description: 'Screen Capture', event: this.toggleRecording, icon: 'expand-arrows-alt' as any }];
ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' });
};
@@ -170,12 +172,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@computed get content() {
return (
<video
- className={'videoBox-content'}
+ className="videoBox-content"
key="video"
ref={r => {
this._videoRef = r;
setTimeout(() => {
- if (this.layoutDoc.mediaState === media_state.PendingRecording && this._videoRef) {
+ if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) {
this.toggleRecording();
}
}, 100);
@@ -183,7 +185,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
autoPlay={this._screenCapture}
style={{ width: this._screenCapture ? '100%' : undefined, height: this._screenCapture ? '100%' : undefined }}
onCanPlay={this.videoLoad}
- controls={true}
+ controls
onClick={e => e.preventDefault()}>
<source type="video/mp4" />
Not supported.
@@ -220,23 +222,23 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
toggleRecording = async () => {
if (!this._screenCapture) {
this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
- 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 audChunks: any = [];
+ this._audioRec.ondataavailable = (e: any) => audChunks.push(e.data);
+ this._audioRec.onstop = async () => {
+ const [{ result }] = await Networking.UploadFilesToServer(audChunks.map((file: any) => ({ file })));
if (!(result instanceof Error)) {
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 = [];
+ const vidChunks: any = [];
this._videoRec.onstart = () => {
if (this.dataDoc[this._props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
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) => {
+ this._videoRec.ondataavailable = (e: any) => vidChunks.push(e.data);
+ this._videoRec.onstop = async () => {
const presentation = TrackMovements.Instance.yieldPresentation();
if (presentation?.movements) {
const presCopy = { ...presentation };
@@ -244,7 +246,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
}
TrackMovements.Instance.finish();
- const file = new File(vid_chunks, `${this.Document[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
+ const file = new File(vidChunks, `${this.Document[Id]}.mkv`, { type: vidChunks[0].type, lastModified: Date.now() });
const [{ result }] = await Networking.UploadFilesToServer({ file });
this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
if (!(result instanceof Error)) {
@@ -297,6 +299,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div className="videoBox-viewer">
<div style={{ position: 'relative', height: this.videoPanelHeight() }}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -305,7 +308,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
PanelWidth={this._props.PanelWidth}
focus={this._props.focus}
isSelected={this._props.isSelected}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
select={emptyFunction}
isContentActive={returnFalse}
NativeDimScaling={returnOne}
@@ -324,9 +327,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
{!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
- fieldKey={'text'}
+ fieldKey="text"
PanelHeight={this.formattedPanelHeight}
select={emptyFunction}
isContentActive={emptyFunction}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8c65fd34e..5ebc50a1b 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,8 +1,9 @@
-let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnAlways, returnEmptyString } from '../../../Utils';
+import { returnAlways, returnEmptyString } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -17,10 +18,12 @@ import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { OverlayView } from '../OverlayView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
import { DocumentIconContainer } from './DocumentIcon';
import './ScriptingBox.scss';
+
const _global = (window /* browser */ || global) /* node */ as any;
+const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
@observer
export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -79,26 +82,24 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@computed({ keepAlive: true }) get rawScript() {
return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? '';
}
- @computed({ keepAlive: true }) get functionName() {
- return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
- }
- @computed({ keepAlive: true }) get functionDescription() {
- return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
- }
- @computed({ keepAlive: true }) get compileParams() {
- return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
- }
-
set rawScript(value) {
this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value);
}
+ @computed({ keepAlive: true }) get functionName() {
+ return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
+ }
set functionName(value) {
this.dataDoc[this.fieldKey + '-functionName'] = value;
}
+ @computed({ keepAlive: true }) get functionDescription() {
+ return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
+ }
set functionDescription(value) {
this.dataDoc[this.fieldKey + '-functionDescription'] = value;
}
-
+ @computed({ keepAlive: true }) get compileParams() {
+ return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+ }
set compileParams(value) {
this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
}
@@ -107,9 +108,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (typeof result === 'object') {
const text = descrip ? result[1] : result[2];
return text !== undefined ? text : '';
- } else {
- return '';
}
+ return '';
}
onClickScriptDisable = returnAlways;
@@ -118,19 +118,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
componentDidMount() {
this._props.setContentViewBox?.(this);
this.rawText = this.rawScript;
- const observer = new _global.ResizeObserver(
- action((entries: any) => {
+ const resizeObserver = new _global.ResizeObserver(
+ action(() => {
const area = document.querySelector('textarea');
if (area) {
- for (const {} of entries) {
- const getCaretCoordinates = require('textarea-caret');
- const caret = getCaretCoordinates(area, this._selection);
- this.resetSuggestionPos(caret);
- }
+ // eslint-disable-next-line global-require
+ const getCaretCoordinates = require('textarea-caret');
+ const caret = getCaretCoordinates(area, this._selection);
+ this.resetSuggestionPos(caret);
}
})
);
- observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
+ resizeObserver.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
}
@action
@@ -138,12 +137,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
const suggestionWidth = this._suggestionRef.current.offsetWidth;
const scriptWidth = this._scriptTextRef.current.offsetWidth;
- const top = caret.top;
- const x = this.dataDoc.x;
- let left = caret.left;
+ const { top } = caret;
+ const { x } = this.dataDoc;
+ let { left } = caret;
if (left + suggestionWidth > x + scriptWidth) {
const diff = left + suggestionWidth - (x + scriptWidth);
- left = left - diff;
+ left -= diff;
}
this._suggestionBoxX = left;
@@ -155,7 +154,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => {
- //used for stacking and masonry view
+ // used for stacking and masonry view
if (ele) {
this.dropDisposer?.();
this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
@@ -164,7 +163,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// only included in buttons, transforms scripting UI to a button
@action
- onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout');
+ onFinish = () => {
+ this.layoutDoc.layout_fieldKey = 'layout';
+ };
// displays error message
@action
@@ -176,7 +177,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
onCompile = () => {
const params: ScriptParam = {};
- this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
+ this.compileParams.forEach(p => {
+ params[p.split(':')[0].trim()] = p.split(':')[1].trim();
+ });
const result = !this.rawText.trim()
? ({ compiled: false, errors: undefined } as any)
@@ -196,7 +199,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
onRun = () => {
if (this.onCompile()) {
const bindings: { [name: string]: any } = {};
- this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key]));
+ this.paramsNames.forEach(key => {
+ bindings[key] = this.dataDoc[key];
+ });
// binds vars so user doesnt have to refer to everything as this.<var>
ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ ...bindings, this: this.Document }, this.onError);
}
@@ -247,7 +252,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc.name = this.functionName;
this.dataDoc.description = this.functionDescription;
- //this.dataDoc.parameters = this.compileParams;
+ // this.dataDoc.parameters = this.compileParams;
this.dataDoc.script = this.rawScript;
ScriptManager.Instance.addScript(this.dataDoc);
@@ -255,6 +260,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._scriptKeys = ScriptingGlobals.getGlobals();
this._scriptingDescriptions = ScriptingGlobals.getDescriptions();
this._scriptingParams = ScriptingGlobals.getParameters();
+ return undefined;
};
// overlays document numbers (ex. d32) over all documents when clicked on
@@ -267,7 +273,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
if (de.complete.docDragData) {
- de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc));
+ de.complete.docDragData.droppedDocuments.forEach(doc => {
+ this.dataDoc[fieldKey] = doc;
+ });
e.stopPropagation();
return true;
}
@@ -285,8 +293,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// sets field of the param name to the selected value in drop down box
@action
viewChanged = (e: React.ChangeEvent, name: string) => {
- //@ts-ignore
- const val = e.target.selectedOptions[0].value;
+ const val = (e.target as any).selectedOptions[0].value;
this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
};
@@ -306,8 +313,26 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
};
renderFunctionInputs() {
- const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />;
- const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
+ const descriptionInput = (
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => {
+ this.functionDescription = e.target.value;
+ }}
+ placeholder="enter description here"
+ value={this.functionDescription}
+ />
+ );
+ const nameInput = (
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => {
+ this.functionName = e.target.value;
+ }}
+ placeholder="enter name here"
+ value={this.functionName}
+ />
+ );
return (
<div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
@@ -339,7 +364,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={14}
@@ -371,7 +396,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={14}
@@ -404,6 +429,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
onChange={e => this.viewChanged(e, parameter)}
value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
{types.map((type, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
{' '}
{type.toString()}{' '}
@@ -496,6 +522,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
suggestionPos = () => {
+ // eslint-disable-next-line global-require
const getCaretCoordinates = require('textarea-caret');
const This = this;
document.querySelector('textarea')?.addEventListener('input', function () {
@@ -509,7 +536,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
keyHandler(e: any, pos: number) {
e.stopPropagation();
if (this._lastChar === 'Enter') {
- this.rawText = this.rawText + ' ';
+ this.rawText += ' ';
}
if (e.key === '(') {
this.suggestionPos();
@@ -524,20 +551,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
} else if (e.key === ')') {
this._paramSuggestion = false;
- } else {
- if (e.key === 'Backspace') {
- if (this._lastChar === '(') {
- this._paramSuggestion = false;
- } else if (this._lastChar === ')') {
- if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
- if (this._scriptParamsText.length > 0) {
- this._paramSuggestion = true;
- }
+ } else if (e.key === 'Backspace') {
+ if (this._lastChar === '(') {
+ this._paramSuggestion = false;
+ } else if (this._lastChar === ')') {
+ if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
+ if (this._scriptParamsText.length > 0) {
+ this._paramSuggestion = true;
}
}
- } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
- this._paramSuggestion = false;
}
+ } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
+ this._paramSuggestion = false;
}
this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
@@ -559,9 +584,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
parameters.forEach((element: string, i: number) => {
if (i < numEntered - 1) {
- first = first + element;
+ first += element;
} else if (i > numEntered - 1) {
- last = last + element;
+ last += element;
}
});
@@ -598,16 +623,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
placeholder="write your script here"
onFocus={this.onFocus}
onBlur={() => this._overlayDisposer?.()}
- onChange={action((e: any) => (this.rawText = e.target.value))}
+ onChange={action((e: any) => {
+ this.rawText = e.target.value;
+ })}
value={this.rawText}
- movePopupAsYouType={true}
+ movePopupAsYouType
loadingComponent={() => <span>Loading</span>}
trigger={{
' ': {
dataProvider: (token: any) => this.handleToken(token),
- component: (blob: any) => {
- return this.renderFuncListElement(blob.entity);
- },
+ component: (blob: any) => this.renderFuncListElement(blob.entity),
output: (item: any, trigger: any) => {
this._spaced = true;
return trigger + item.trim();
@@ -615,9 +640,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
},
'.': {
dataProvider: (token: any) => this.handleToken(token),
- component: (blob: any) => {
- return this.renderFuncListElement(blob.entity);
- },
+ component: (blob: any) => this.renderFuncListElement(blob.entity),
output: (item: any, trigger: any) => {
this._spaced = true;
return trigger + item.trim();
@@ -653,16 +676,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// params box on bottom
const parameterInput = (
<div className="scriptingBox-params">
- <EditableView
- display={'block'}
- maxHeight={72}
- height={35}
- fontSize={22}
- contents={''}
- GetValue={returnEmptyString}
- SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)}
- placeholder={'enter parameters here'}
- />
+ <EditableView display="block" maxHeight={72} height={35} fontSize={22} contents="" GetValue={returnEmptyString} SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} placeholder="enter parameters here" />
</div>
);
@@ -670,13 +684,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const definedParameters = !this.compileParams.length ? null : (
<div className="scriptingBox-plist" style={{ width: '30%' }}>
{this.compileParams.map((parameter, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={12}
- background-color={'beige'}
+ background-color="beige"
contents={parameter}
GetValue={() => parameter}
SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))}
@@ -749,6 +764,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
{!this.compileParams.length || !this.paramsNames ? null : (
<div className="scriptingBox-plist">
{this.paramsNames.map((parameter: string, i: number) => (
+ // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
<div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
index e19f28f08..b9f215d77 100644
--- a/src/client/views/nodes/SliderBox-components.tsx
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { SliderItem } from "react-compound-slider";
-import "./SliderBox-tooltip.css";
+import * as React from 'react';
+import { SliderItem } from 'react-compound-slider';
+import './SliderBox-tooltip.css';
const { Component, Fragment } = React;
@@ -8,24 +8,24 @@ const { Component, Fragment } = React;
// TOOLTIP RAIL
// *******************************************************
const railStyle: React.CSSProperties = {
- position: "absolute",
- width: "100%",
+ position: 'absolute',
+ width: '100%',
height: 40,
borderRadius: 7,
- cursor: "pointer",
+ cursor: 'pointer',
opacity: 0.3,
zIndex: 300,
- border: "1px solid grey"
+ border: '1px solid grey',
};
const railCenterStyle: React.CSSProperties = {
- position: "absolute",
- width: "100%",
+ position: 'absolute',
+ width: '100%',
height: 14,
borderRadius: 7,
- cursor: "pointer",
- pointerEvents: "none",
- backgroundColor: "rgb(155,155,155)"
+ cursor: 'pointer',
+ pointerEvents: 'none',
+ backgroundColor: 'rgb(155,155,155)',
};
interface TooltipRailProps {
@@ -37,21 +37,21 @@ interface TooltipRailProps {
export class TooltipRail extends Component<TooltipRailProps> {
state = {
value: null,
- percent: null
+ percent: null,
};
static defaultProps = {
- disabled: false
+ disabled: false,
};
onMouseEnter = () => {
- document.addEventListener("mousemove", this.onMouseMove);
- }
+ document.addEventListener('mousemove', this.onMouseMove);
+ };
onMouseLeave = () => {
this.setState({ value: null, percent: null });
- document.removeEventListener("mousemove", this.onMouseMove);
- }
+ document.removeEventListener('mousemove', this.onMouseMove);
+ };
onMouseMove = (e: Event) => {
const { activeHandleID, getEventData } = this.props;
@@ -61,7 +61,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
} else {
this.setState(getEventData(e));
}
- }
+ };
render() {
const { value, percent } = this.state;
@@ -73,11 +73,10 @@ export class TooltipRail extends Component<TooltipRailProps> {
<div
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-35px"
- }}
- >
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-35px',
+ }}>
<div className="tooltip">
<span className="tooltiptext">Value: {value}</span>
</div>
@@ -87,7 +86,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
style={railStyle}
{...getRailProps({
onMouseEnter: this.onMouseEnter,
- onMouseLeave: this.onMouseLeave
+ onMouseLeave: this.onMouseLeave,
})}
/>
<div style={railCenterStyle} />
@@ -110,20 +109,20 @@ interface HandleProps {
export class Handle extends Component<HandleProps> {
static defaultProps = {
- disabled: false
+ disabled: false,
};
state = {
- mouseOver: false
+ mouseOver: false,
};
onMouseEnter = () => {
this.setState({ mouseOver: true });
- }
+ };
onMouseLeave = () => {
this.setState({ mouseOver: false });
- }
+ };
render() {
const {
@@ -131,7 +130,7 @@ export class Handle extends Component<HandleProps> {
handle: { id, value, percent },
isActive,
disabled,
- getHandleProps
+ getHandleProps,
} = this.props;
const { mouseOver } = this.state;
@@ -141,11 +140,10 @@ export class Handle extends Component<HandleProps> {
<div
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-35px"
- }}
- >
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-35px',
+ }}>
<div className="tooltip">
<span className="tooltiptext">Value: {value}</span>
</div>
@@ -158,21 +156,21 @@ export class Handle extends Component<HandleProps> {
aria-valuenow={value}
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-6px",
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-6px',
zIndex: 400,
width: 24,
height: 24,
- cursor: "pointer",
+ cursor: 'pointer',
border: 0,
- borderRadius: "50%",
- boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)",
- backgroundColor: disabled ? "#666" : "#3e1db3"
+ borderRadius: '50%',
+ boxShadow: '1px 1px 1px 1px rgba(0, 0, 0, 0.4)',
+ backgroundColor: disabled ? '#666' : '#3e1db3',
}}
{...getHandleProps(id, {
onMouseEnter: this.onMouseEnter,
- onMouseLeave: this.onMouseLeave
+ onMouseLeave: this.onMouseLeave,
})}
/>
</Fragment>
@@ -190,23 +188,18 @@ interface TrackProps {
getTrackProps: () => object;
}
-export function Track({
- source,
- target,
- getTrackProps,
- disabled = false
-}: TrackProps) {
+export function Track({ source, target, getTrackProps, disabled = false }: TrackProps) {
return (
<div
style={{
- position: "absolute",
+ position: 'absolute',
height: 14,
zIndex: 1,
- backgroundColor: disabled ? "#999" : "#3e1db3",
+ backgroundColor: disabled ? '#999' : '#3e1db3',
borderRadius: 7,
- cursor: "pointer",
+ cursor: 'pointer',
left: `${source.percent}%`,
- width: `${target.percent - source.percent}%`
+ width: `${target.percent - source.percent}%`,
}}
{...getTrackProps()}
/>
@@ -229,25 +222,24 @@ export function Tick({ tick, count, format = defaultFormat }: TickProps) {
<div>
<div
style={{
- position: "absolute",
+ position: 'absolute',
marginTop: 20,
width: 1,
height: 5,
- backgroundColor: "rgb(200,200,200)",
- left: `${tick.percent}%`
+ backgroundColor: 'rgb(200,200,200)',
+ left: `${tick.percent}%`,
}}
/>
<div
style={{
- position: "absolute",
+ position: 'absolute',
marginTop: 25,
fontSize: 10,
- textAlign: "center",
+ textAlign: 'center',
marginLeft: `${-(100 / count) / 2}%`,
width: `${100 / count}%`,
- left: `${tick.percent}%`
- }}
- >
+ left: `${tick.percent}%`,
+ }}>
{format(tick.value)}
</div>
</div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4773a21c9..1121134cf 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable jsx-a11y/media-has-caption */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
import * as React from 'react';
+import { ClientUtils, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -10,11 +12,11 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, formatTime } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ReplayMovements } from '../../util/ReplayMovements';
@@ -23,14 +25,14 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import { RecordingBox } from './RecordingBox';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
import './VideoBox.scss';
/**
@@ -152,11 +154,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
clearTimeout(this._controlsFadeTimer);
this._scrubbing = true;
this._controlsFadeTimer = setTimeout(
- action(() => (this._scrubbing = false)),
+ action(() => {
+ this._scrubbing = false;
+ }),
500
);
e.stopPropagation();
break;
+ default:
}
}
};
@@ -203,7 +208,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
else {
this._keepCurrentlyPlaying = true;
this.pause();
- setTimeout(() => (this._keepCurrentlyPlaying = false));
+ setTimeout(() => {
+ this._keepCurrentlyPlaying = false;
+ });
}
};
@@ -246,7 +253,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
clearTimeout(this._controlsFadeTimer);
this._controlsVisible = true;
this._controlsFadeTimer = setTimeout(
- action(() => (this._controlsVisible = false)),
+ action(() => {
+ this._controlsVisible = false;
+ }),
3000
);
}
@@ -262,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
e.target,
e,
- action((e, down, delta) => {
+ action((moveEv, down, delta) => {
if (this._controlsTransform) {
this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
@@ -280,7 +289,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1);
- const ctx = canvas.getContext('2d'); //draw image to canvas. scale to target dimensions
+ const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions
if (ctx) {
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
@@ -297,13 +306,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.addDocument?.(b);
DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
} else {
- //convert to desired file format
+ // convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
- const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
+ const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+ const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
const filename = basename(encodedFilename);
- Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
+ ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
}
};
@@ -318,7 +327,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
// creates link for snapshot
createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => {
- const url = !imagePath.startsWith('/') ? Utils.CorsProxy(imagePath) : imagePath;
+ const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath;
const width = NumCast(this.layoutDoc._width) || 1;
const height = NumCast(this.layoutDoc._height);
const imageSnapshot = Docs.Create.ImageDocument(url, {
@@ -334,7 +343,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
- const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
+ DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
// link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
@@ -345,7 +354,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
const anchor =
addAsAnnotation && marquee
- ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
+ ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document
: Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
return anchor;
@@ -375,12 +384,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
// do we want to always open up the timeline when followin a link? kind of clunky visually
- //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ // this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
options.didMove = true;
}
return this._stackedTimeline.getView(doc, options);
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
// extracts video thumbnails and saves them as field of doc
@@ -390,21 +401,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const thumbnailPromises: Promise<any>[] = [];
const video = document.createElement('video');
- video.onloadedmetadata = () => (video.currentTime = 0);
+ video.onloadedmetadata = () => {
+ video.currentTime = 0;
+ };
video.onseeked = () => {
const canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 100;
canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
- const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
+ const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
- thumbnailPromises?.push(Utils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
+ thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
if (newTime < video.duration) {
video.currentTime = newTime;
} else {
- Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails)));
+ Promise.all(thumbnailPromises).then(thumbnails => {
+ this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails);
+ });
}
};
@@ -423,11 +438,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._disposers.reactionDisposer?.();
this._disposers.reactionDisposer = reaction(
() => NumCast(this.layoutDoc._layout_currentTimecode),
- time => !this._playing && (vref.currentTime = time),
+ time => {
+ !this._playing && (vref.currentTime = time);
+ },
{ fireImmediately: true }
);
- (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length != VideoBox.numThumbnails) && this.getVideoThumbnails();
+ (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoBox.numThumbnails) && this.getVideoThumbnails();
}
};
@@ -436,7 +453,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setContentRef = (cref: HTMLDivElement | null) => {
this._contentRef = cref;
if (cref) {
- cref.onfullscreenchange = action(e => {
+ cref.onfullscreenchange = action(() => {
this._fullScreen = document.fullscreenElement === cref;
this._controlsVisible = true;
this._scrubbing = false;
@@ -451,7 +468,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
// context menu
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
@@ -462,25 +479,41 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
subitems.push({
description: 'Screen Capture',
event: async () => {
- runInAction(() => (this._screenCapture = !this._screenCapture));
+ runInAction(() => {
+ this._screenCapture = !this._screenCapture;
+ });
this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
},
icon: 'expand-arrows-alt',
});
- subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' });
+ subitems.push({
+ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
+ event: () => {
+ this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks;
+ },
+ icon: 'expand-arrows-alt',
+ });
subitems.push({
description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
- event: () => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ event: () => {
+ this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks;
+ },
+ icon: 'expand-arrows-alt',
+ });
+ subitems.push({
+ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick',
+ event: () => {
+ this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors;
+ },
icon: 'expand-arrows-alt',
});
- subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
// subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
// subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
// subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
subitems.push({
description: 'Copy path',
event: () => {
- Utils.CopyText(url);
+ ClientUtils.CopyText(url);
},
icon: 'expand-arrows-alt',
});
@@ -504,7 +537,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
// ref for updating time
- setAudioRef = (e: HTMLAudioElement | null) => (this._audioPlayer = e);
+ setAudioRef = (e: HTMLAudioElement | null) => {
+ this._audioPlayer = e;
+ };
// renders the video and audio
@computed get content() {
@@ -570,8 +605,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- e => {
- this.Snapshot(e.clientX, e.clientY);
+ moveEv => {
+ this.Snapshot(moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
@@ -586,7 +621,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- action(encodeURIComponent => {
+ action(() => {
this._clicking = false;
if (this._props.isContentActive()) {
// const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
@@ -600,7 +635,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
() => {
this.layoutDoc._layout_timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
setTimeout(
- action(() => (this._clicking = false)),
+ action(() => {
+ this._clicking = false;
+ }),
500
);
},
@@ -632,11 +669,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
// for annotating, adds doc with time info
@action.bound
- addDocWithTimecode(doc: Doc | Doc[]): boolean {
- const docs = doc instanceof Doc ? [doc] : doc;
+ addDocWithTimecode(docIn: Doc | Doc[]): boolean {
+ const docs = docIn instanceof Doc ? [docIn] : docIn;
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
- docs.forEach(doc => (doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1));
- return this.addDocument(doc);
+ docs.forEach(doc => {
+ doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1;
+ });
+ return this.addDocument(docs);
}
// play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@@ -644,7 +683,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._playRegionTimer);
this._playRegionTimer = undefined;
- if (Number.isNaN(this.player?.duration)) {
+ if (this.player?.duration === undefined || isNaN(this.player.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this.player) {
// trimBounds override requested playback bounds
@@ -696,7 +735,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
e,
returnFalse,
returnFalse,
- action((e: PointerEvent, doubleTap?: boolean) => {
+ action((clickEv: PointerEvent, doubleTap?: boolean) => {
if (doubleTap) {
this.startTrim(TrimScope.All);
} else if (this.timeline) {
@@ -731,11 +770,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
// stretches vertically or horizontally depending on video orientation so video fits full screen
fullScreenSize() {
if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
- //prettier-ignore
- return ({ height: '100%' });
+ return { height: '100%' };
}
- //prettier-ignore
- return ({ width: '100%' });
+ return ({ width: '100%' }); // prettier-ignore
}
// for zoom slider, sets timeline waveform zoom
@@ -757,9 +794,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -777,7 +814,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.select(true);
};
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ this._props.whenChildContentsActiveChanged(isActive);
+ });
timelineScreenToLocal = () =>
this._props
@@ -785,7 +825,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
.scale(this.scaling())
.translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
- setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
+ setPlayheadTime = (time: number) => {
+ this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time;
+ };
timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
@@ -806,7 +848,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
- timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
+ timelineDocFilter = () => [`_isTimelineLabel:true,${ClientUtils.noRecursionHack}:x`];
// renders video controls
componentUI = (boundsLeft: number, boundsTop: number) => {
@@ -848,7 +890,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
return (
<div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
<CollectionStackedTimeline
- ref={action((r: any) => (this._stackedTimeline = r))}
+ ref={action((r: any) => {
+ this._stackedTimeline = r;
+ })}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
@@ -886,7 +931,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
const regionData = region[DocData];
regionData.backgroundColor = 'transparent';
@@ -915,8 +960,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
croppingProto.type = DocumentType.VID;
croppingProto.layout = VideoBox.LayoutString('data');
croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
croppingProto.videoCrop = true;
croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
croppingProto.freeform_scale = viewScale;
@@ -958,14 +1003,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
left: (this._props.PanelWidth() - this.panelWidth()) / 2,
}}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
+ isAnnotationOverlay
+ annotationLayerHostsContent
PanelWidth={this._props.PanelWidth}
PanelHeight={this._props.PanelHeight}
isAnyChildContentActive={returnFalse}
@@ -1049,12 +1095,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
</div>
)}
- <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
+ <div className="videobox-button" title="full screen" onPointerDown={this.onFullDown}>
<FontAwesomeIcon icon="expand" />
</div>
{!this._fullScreen && width > 300 && (
- <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
+ <div className="videobox-button" title="show timeline" onPointerDown={this.onTimelineHdlDown}>
<FontAwesomeIcon icon="eye" />
</div>
)}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 033b01d24..fc2e4bf61 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { htmlToText } from 'html-to-text';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { HtmlField } from '../../../fields/HtmlField';
import { InkTool } from '../../../fields/InkField';
@@ -14,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
+import { emptyFunction, stringHash } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -24,7 +27,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -36,10 +39,11 @@ import { StyleProp } from '../StyleProvider';
import { DocumentView, OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import { LinkInfo } from './LinkDocPreview';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
import './WebBox.scss';
+
const { CreateImage } = require('./WebBoxRenderer');
-const _global = (window /* browser */ || global) /* node */ as any;
+
@observer
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
@@ -141,19 +145,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
- var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
if (!htmlString) {
- htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+ htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text();
}
this.layoutDoc.thumb = undefined;
this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
- .then((data_url: any) => {
- if (data_url.includes('<!DOCTYPE')) {
+ .then((dataUrl: any) => {
+ if (dataUrl.includes('<!DOCTYPE')) {
console.log('BAD DATA IN THUMB CREATION');
return;
}
- Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
+ ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
setTimeout(
action(() => {
this.Document.thumbLockout = false;
@@ -166,7 +170,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)
);
})
- .catch(function (error: any) {
+ .catch((error: any) => {
console.error('oops, something went wrong!', error);
});
};
@@ -187,7 +191,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
});
this._disposers.urlchange = reaction(
() => WebCast(this.dataDoc.data),
- url => this.submitURL(false, false)
+ () => this.submitURL(false, false)
);
this._disposers.titling = reaction(
() => StrCast(this.Document.title),
@@ -199,8 +203,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._disposers.layout_autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight => {
- if (layout_autoHeight) {
+ layoutAutoHeight => {
+ if (layoutAutoHeight) {
this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
@@ -219,8 +223,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
} // else it's an HTMLfield
} else if (this.webField && !this.dataDoc.text) {
- WebRequest.get(Utils.CorsProxy(this.webField.href)) //
- .then(result => result && (this.dataDoc.text = htmlToText(result.content)));
+ WebRequest.get(ClientUtils.CorsProxy(this.webField.href)) //
+ .then(result => {
+ result && (this.dataDoc.text = htmlToText(result.content));
+ });
}
this._disposers.scrollReaction = reaction(
@@ -254,7 +260,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current);
+ const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : ClientUtils.GetScreenTransform(this._mainCont.current);
if (rect && rect.width !== this._mainCont.current.clientWidth) {
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
@@ -283,27 +289,39 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
focus = (anchor: Doc, options: FocusViewOptions) => {
if (anchor !== this.Document && this._outerRef.current) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
+ const scrollTo = ClientUtils.scrollIntoView(
+ NumCast(anchor.y),
+ NumCast(anchor._height),
+ NumCast(this.layoutDoc._layout_scrollTop),
+ windowHeight,
+ windowHeight * 0.1,
+ Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)
+ );
if (scrollTo !== undefined) {
if (this._initialScroll === undefined) {
const focusTime = options.zoomTime ?? 500;
this.goTo(scrollTo, focusTime, options.easeFunc);
return focusTime;
- } else {
- this._initialScroll = scrollTo;
}
+ this._initialScroll = scrollTo;
}
}
+ return undefined;
};
@action
- getView = (doc: Doc, options: FocusViewOptions) => {
- if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+ getView = (doc: Doc /* , options: FocusViewOptions */) => {
+ if (Doc.AreProtosEqual(doc, this.Document))
+ return new Promise<Opt<DocumentView>>(res => {
+ res(this.DocumentView?.());
+ });
if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
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)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
@@ -314,14 +332,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return this._props.addDocTab(doc, where);
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
try {
const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents();
if (contents) {
ele = document.createElement('div');
ele.append(contents);
}
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
const visibleAnchor = this._getAnchor(this._savedAnnotations, true);
const anchor =
visibleAnchor ??
@@ -330,7 +350,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
y: NumCast(this.layoutDoc._layout_scrollTop),
annotationOn: this.Document,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.Document);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: !!pinProps?.pinData, pannable: true } }, this.Document);
anchor.text = ele?.textContent ?? '';
anchor.text_html = ele?.innerHTML ?? this._selectionText;
addAsAnnotation && this.addDocumentWrapper(anchor);
@@ -356,7 +376,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._textAnnotationCreator = undefined;
this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
- const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!);
const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
@@ -387,7 +407,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e?.stopPropagation();
setTimeout(() => {
// if menu comes up right away, the down event can still be active causing a menu item to be selected
- this.specificContextMenu(undefined as any);
+ this.specificContextMenu();
this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
});
}
@@ -462,6 +482,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const sheets = document.head.appendChild(style);
return (sheets as any).sheet;
}
+ return undefined;
}
addWebStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
const propText =
@@ -476,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
_iframetimeout: any = undefined;
@observable _warning = 0;
@action
- iframeLoaded = (e: any) => {
+ iframeLoaded = () => {
const iframe = this._iframe;
if (this._initialScroll !== undefined) {
this.setScrollPos(this._initialScroll);
@@ -491,7 +512,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
runInAction(() => this._warning++);
href = undefined;
}
- let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
+ let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
if (requrlraw !== this._url.toString()) {
if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
@@ -553,7 +574,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const batch = UndoManager.StartBatch('webclick');
e.stopPropagation();
setTimeout(() => {
- this.setData(href.replace(Utils.prepend(''), origin));
+ this.setData(href.replace(ClientUtils.prepend(''), origin));
batch.end();
});
if (this._outerRef.current) {
@@ -632,12 +653,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._scrollHeight = 0;
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => (this._webUrl = this._url)));
+ setTimeout(
+ action(() => {
+ this._webUrl = this._url;
+ })
+ );
} else {
this._webUrl = this._url;
}
return true;
}
+ return undefined;
});
return false;
};
@@ -655,12 +681,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._scrollHeight = 0;
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => (this._webUrl = this._url)));
+ setTimeout(
+ action(() => {
+ this._webUrl = this._url;
+ })
+ );
} else {
this._webUrl = this._url;
}
return true;
}
+ return undefined;
});
return false;
};
@@ -692,13 +723,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const html = dataTransfer.getData('text/html');
const uri = dataTransfer.getData('text/uri-list');
const url = uri || html || this._url || '';
- const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url;
+ const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^/]*/)?.[0] || '') : url;
this.setData(newurl);
e.stopPropagation();
};
@action
- setData = (data: Field | Promise<RefField | undefined>) => {
+ setData = (data: FieldType | Promise<RefField | undefined>) => {
if (!(typeof data === 'string') && !(data instanceof WebField)) return false;
if (Field.toString(data) === this._url) return false;
this._scrollHeight = 0;
@@ -715,19 +746,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.stopPropagation();
};
- specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
if (!cm.findByDescription('Options...')) {
!Doc.noviceMode &&
- funcs.push({ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors']), icon: 'snowflake' });
+ funcs.push({
+ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors',
+ event: () => {
+ this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors'];
+ },
+ icon: 'snowflake',
+ });
funcs.push({
description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
event: () => {
this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
if (this._iframe) {
- runInAction(() => (this._hackHide = true));
- setTimeout(action(() => (this._hackHide = false)));
+ runInAction(() => {
+ this._hackHide = true;
+ });
+ setTimeout(
+ action(() => {
+ this._hackHide = false;
+ })
+ );
}
},
icon: 'snowflake',
@@ -765,7 +808,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(() => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
return true;
}),
@@ -789,7 +832,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@observable lighttext = false;
@computed get urlContent() {
- if (this.ScreenToLocalBoxXf().Scale > 25) return <div></div>;
+ if (this.ScreenToLocalBoxXf().Scale > 25) return <div />;
setTimeout(
action(() => {
if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -816,14 +859,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
if (field instanceof WebField) {
- const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl;
+ const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl;
const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
- //if (!scripts) console.log('No scripts for: ' + url);
+ // if (!scripts) console.log('No scripts for: ' + url);
return (
<iframe
+ title="web iframe"
key={this._warning}
className="webBox-iframe"
- ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
style={{ pointerEvents: SnappingManager.IsResizing ? 'none' : undefined }}
src={url}
onLoad={this.iframeLoaded}
@@ -834,11 +880,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
/>
);
}
- return <iframe className="webBox-iframe" ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} src={'https://crossorigin.me/https://cs.brown.edu'} />;
+ return (
+ <iframe
+ title="web frame"
+ className="webBox-iframe"
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
+ src="https://crossorigin.me/https://cs.brown.edu"
+ />
+ );
}
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- this._url && (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
+ this._url &&
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => {
+ doc.config_data = new WebField(this._url);
+ });
return this.addDocument(doc, annotationKey);
};
@@ -892,14 +950,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@observable _previewNativeWidth: Opt<number> = undefined;
@observable _previewWidth: Opt<number> = undefined;
toggleSidebar = action((preview: boolean = false) => {
- var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
+ let nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
if (!nativeWidth) {
const defaultNativeWidth = NumCast(this.Document.nativeWidth, this.dataDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
@@ -938,7 +996,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
_innerCollectionView: CollectionFreeFormView | undefined;
zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
- setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
+ setInnerContent = (component: ViewBoxInterface) => {
+ this._innerCollectionView = component as CollectionFreeFormView;
+ };
@computed get content() {
const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -969,24 +1029,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
{this.inlineTextAnnotations
.sort((a, b) => NumCast(a.y) - NumCast(b.y))
.map(anno => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
<Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
}
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
}
renderAnnotations = (childFilters: () => string[]) => (
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={this.setInnerContent}
NativeWidth={returnZero}
NativeHeight={returnZero}
originTopLeft={false}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlayScrollable
renderDepth={this._props.renderDepth + 1}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
fieldKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
PanelWidth={this.panelWidth}
@@ -1029,7 +1091,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}}
// when active, block wheel events from propagating since they're handled by the iframe
onWheel={this.onZoomWheel}
- onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
+ onScroll={() => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}>
<div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
{this.content}
@@ -1045,7 +1107,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return (
<div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="webBox-overlayButton" title={'search'} />
+ <button type="button" className="webBox-overlayButton" title="search" />
<input
className="webBox-searchBar"
placeholder="Search"
@@ -1056,13 +1118,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.stopPropagation();
}}
/>
- <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <button type="button" className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
</div>
<button
+ type="button"
className="webBox-overlayButton"
- title={'search'}
+ title="search"
onClick={action(() => {
this._searching = !this._searching;
this.search('', false, true);
@@ -1075,14 +1138,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
</div>
);
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._searchString = e.currentTarget.value;
+ };
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [ClientUtils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc)) return 'none';
@@ -1149,6 +1216,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
fieldKey={this.fieldKey + '_' + this._urlHash}
@@ -1169,6 +1237,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function urlHash(url: string) {
return stringHash(url);
});
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 7fd799952..2d1d3d7db 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,8 +7,8 @@ import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { Cast } from '../../../../fields/Types';
import { numberRange } from '../../../../Utils';
+import { Colors } from '../../global/globalEnums';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { Colors } from './../../global/globalEnums';
import './AudioWaveform.scss';
import { WaveCanvas } from './WaveCanvas';
@@ -62,7 +62,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
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;
+ audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets//' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
componentWillUnmount() {
this._disposer?.();
@@ -72,7 +72,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
this._disposer = reaction(
() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
- if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+ if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey !== 'layout_icon') {
// setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
@@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
progressColor={Colors.MEDIUM_BLUE_ALT}
progress={this._props.progress ?? 1}
barWidth={200 / this.audioBuckets.length}
- //gradientColors={this._props.gradientColors}
+ // gradientColors={this._props.gradientColors}
peaks={this.audioBuckets}
width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
height={this.waveHeight * window.devicePixelRatio}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
index d3f5669a2..eacda2d42 100644
--- a/src/client/views/nodes/audio/WaveCanvas.tsx
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import React from 'react';
interface WaveCanvasProps {
@@ -14,7 +15,7 @@ interface WaveCanvasProps {
export class WaveCanvas extends React.Component<WaveCanvasProps> {
// If the first value of peaks is negative, addToIndices will be 1
- posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+ posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 === 0);
drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
// Bar wave draws the bottom only as a reflection of the top,
@@ -47,6 +48,7 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
// A half-pixel offset makes lines crisp
const $ = 0.5 / this.props.pixelRatio;
+ // eslint-disable-next-line no-bitwise
const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
const scale = width / length;
@@ -55,14 +57,14 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
waveCanvasCtx.beginPath();
waveCanvasCtx.moveTo($, halfH);
- for (var i = 0; i < length; i++) {
- var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+ for (let i = 0; i < length; i++) {
+ const h = Math.round((allPeaks[2 * i] / absmax) * halfH);
waveCanvasCtx.lineTo(i * scale + $, halfH - h);
}
// Draw the bottom edge going backwards, to make a single closed hull to fill.
- for (var i = length - 1; i >= 0; i--) {
- var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+ for (let i = length - 1; i >= 0; i--) {
+ const h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
waveCanvasCtx.lineTo(i * scale + $, halfH - h);
}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 748c3322e..5893c346f 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -4,7 +4,7 @@ import multiMonthPlugin from '@fullcalendar/multimonth';
import { makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { dateRangeStrToDates } from '../../../../Utils';
+import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -57,12 +57,13 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
docBackgroundColor(type: string): string {
// TODO: Return a different color based on the event type
+ console.log(type);
return 'blue';
}
get calendarEvents(): EventSourceInput | undefined {
if (this.childDocs.length === 0) return undefined;
- return this.childDocs.map((doc, idx) => {
+ return this.childDocs.map(doc => {
const docTitle = StrCast(doc.title);
const docDateRange = StrCast(doc.date_range);
const [startDate, endDate] = dateRangeStrToDates(docDateRange);
@@ -85,7 +86,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
}
- handleEventClick = (arg: EventClickArg) => {
+ handleEventClick = (/* arg: EventClickArg */) => {
// TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
};
@@ -113,7 +114,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
return (
<div className="calendar-box-conatiner">
- <div id="calendar-box-v1"></div>
+ <div id="calendar-box-v1" />
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index a72ed1813..3ec49fa27 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,60 +1,11 @@
import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { DocServer } from '../../../DocServer';
import * as React from 'react';
import { IReactionDisposer, computed, reaction } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
import { NumCast } from '../../../../fields/Types';
-// creates an inline comment in a note when '>>' is typed.
-// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
-// the comment can be toggled on/off with the '<-' text anchor.
-export class DashDocCommentView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
-
- constructor(node: any, view: any, getPos: any) {
- this.node = node;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.fontWeight = 'bold';
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- this.dom.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
- (this as any).dom = this.dom;
- }
-
- setHeight = (hgt: number) => {
- !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
- };
-
- destroy() {
- this.root.unmount();
- }
- deselectNode() {
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- this.dom.classList.add('ProseMirror-selectednode');
- }
-}
-
interface IDashDocCommentViewInternal {
docId: string;
view: any;
@@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal {
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
_reactionDisposer: IReactionDisposer | undefined;
- @computed get _dashDoc() {
- return DocServer.GetRefField(this.props.docId);
- }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
}
componentDidMount(): void {
this._reactionDisposer?.();
- this._dashDoc.then(
- doc =>
- doc instanceof Doc &&
- (this._reactionDisposer = reaction(
+ this._dashDoc.then(doc => {
+ if (doc instanceof Doc) {
+ this._reactionDisposer = reaction(
() => NumCast((doc as Doc)._height),
hgt => this.props.setHeight(hgt),
- {
- fireImmediately: true,
- }
- ))
- );
+ { fireImmediately: true }
+ );
+ }
+ });
}
componentWillUnmount(): void {
this._reactionDisposer?.();
}
- onPointerLeaveCollapsed(e: any) {
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
+
+ onPointerLeaveCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerEnterCollapsed(e: any) {
+ onPointerEnterCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerUpCollapsed(e: any) {
+ onPointerUpCollapsed = (e: any) => {
const target = this.targetNode();
if (target) {
const expand = target.hidden;
- const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+ const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
- } catch (e) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
}
e.stopPropagation();
- }
+ };
- onPointerDownCollapsed(e: any) {
+ onPointerDownCollapsed = (e: any) => {
e.stopPropagation();
- }
+ };
targetNode = () => {
// search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- const state = this.props.view.state;
+ const { state } = this.props.view;
for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
const m = state.doc.nodeAt(i);
if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
@@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
setTimeout(() => {
try {
this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
- } catch (e) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
return undefined;
};
@@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}></span>
+ onPointerDown={this.onPointerDownCollapsed}
+ />
);
}
}
+
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+
+ constructor(node: any, view: any, getPos: any) {
+ this.node = node;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
+ (this as any).dom = this.dom;
+ }
+
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow &&
+ DocServer.GetRefField(this.node.attrs.docId).then(doc => {
+ doc instanceof Doc && (this.dom.style.height = hgt + '');
+ });
+ };
+
+ destroy() {
+ this.root.unmount();
+ }
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 7335c9286..f311b3cdd 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,12 +1,13 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { ClientUtils, returnFalse } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { Transform } from '../../../util/Transform';
@@ -15,63 +16,7 @@ import { DocumentView } from '../DocumentView';
import { FocusViewOptions } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
-var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
-export class DashDocView {
- dom: HTMLSpanElement; // container for label and value
- root: any;
-
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this.dom = document.createElement('span');
- this.dom.style.position = 'relative';
- this.dom.style.textIndent = '0';
- this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
- this.dom.style.height = node.attrs.height;
- this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
- (this.dom.style as any).float = node.attrs.float;
- this.dom.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(
- <DashDocViewInternal
- docId={node.attrs.docId}
- embedding={node.attrs.embedding}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />
- );
- }
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- this.dom.style.backgroundColor = '';
- }
- selectNode() {
- this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
- }
-}
-
+const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
interface IDashDocViewInternal {
docId: string;
embedding: string;
@@ -84,6 +29,7 @@ interface IDashDocViewInternal {
node: any;
getPos: any;
}
+
@observer
export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> {
_spanRef = React.createRef<HTMLDivElement>();
@@ -157,7 +103,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
};
outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
@@ -226,3 +172,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
);
}
}
+
+export class DashDocView {
+ dom: HTMLSpanElement; // container for label and value
+ root: any;
+
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this.dom = document.createElement('span');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+ (this.dom.style as any).float = node.attrs.float;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashDocViewInternal
+ docId={node.attrs.docId}
+ embedding={node.attrs.embedding}
+ width={node.attrs.width}
+ height={node.attrs.height}
+ hidden={node.attrs.hidden}
+ fieldKey={node.attrs.fieldKey}
+ tbox={tbox}
+ view={view}
+ node={node}
+ getPos={getPos}
+ />
+ );
+ }
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ this.dom.style.backgroundColor = '';
+ }
+ selectNode() {
+ this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 439d4785e..dc388b22a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,15 +1,20 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { Cast, DocCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
@@ -21,93 +26,70 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection } from 'prosemirror-state';
-export class DashFieldView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
- tbox: FormattedTextBox;
- getpos: any;
- @observable _nodeSelected = false;
- NodeSelected = () => this._nodeSelected;
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
+ static Instance: DashFieldViewMenu;
+ static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ static toggleFieldHide: () => void = emptyFunction;
+ static toggleValueHide: () => void = emptyFunction;
+ constructor(props: any) {
+ super(props);
+ DashFieldViewMenu.Instance = this;
+ }
- unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- makeObservable(this);
- const self = this;
- this.node = node;
- this.tbox = tbox;
- this.getpos = getPos;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- const tBox = this.tbox;
- this.dom.onkeypress = function (e: KeyboardEvent) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: KeyboardEvent) {
- e.stopPropagation();
- if (e.key === 'Tab') {
- e.preventDefault();
- const editor = tbox.EditorView;
- if (editor) {
- const state = editor.state;
- for (var i = self.getpos() + 1; i < state.doc.content.size; i++) {
- if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
- return;
- }
- }
- // tBox.setFocus(state.selection.to);
- }
- }
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
+ showFields = (e: React.MouseEvent) => {
+ DashFieldViewMenu.createFieldView(e);
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleFieldHide = () => {
+ DashFieldViewMenu.toggleFieldHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleValueHide = () => {
+ DashFieldViewMenu.toggleValueHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(
- <DashFieldViewInternal
- node={node}
- unclickable={this.unclickable}
- getPos={getPos}
- fieldKey={node.attrs.fieldKey}
- docId={node.attrs.docId}
- width={node.attrs.width}
- height={node.attrs.height}
- hideKey={node.attrs.hideKey}
- hideValue={node.attrs.hideValue}
- editable={node.attrs.editable}
- nodeSelected={this.NodeSelected}
- tbox={tbox}
- />
+ @observable _fieldKey = '';
+
+ @action
+ public show = (x: number, y: number, fieldKey: string) => {
+ this._fieldKey = fieldKey;
+ this.jumpTo(x, y, true);
+ const hideMenu = () => {
+ this.fadeOut(true);
+ document.removeEventListener('pointerdown', hideMenu, true);
+ };
+ document.addEventListener('pointerdown', hideMenu, true);
+ };
+ render() {
+ return this.getElement(
+ <>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="sm" />
+ </button>
+ </Tooltip>
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+ <FontAwesomeIcon icon="bullseye" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+ <FontAwesomeIcon icon="hashtag" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ </>
);
}
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- runInAction(() => (this._nodeSelected = false));
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
- this.dom.classList.add('ProseMirror-selectednode');
- }
}
-
interface IDashFieldViewInternal {
fieldKey: string;
docId: string;
@@ -137,7 +119,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
makeObservable(this);
this._fieldKey = this._props.fieldKey;
this._textBoxDoc = this._props.tbox.Document;
- const setDoc = action((doc: Doc) => (this._dashDoc = doc));
+ const setDoc = action((doc: Doc) => {
+ this._dashDoc = doc;
+ });
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -172,7 +156,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
- <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
+ <div
+ onClick={action(() => {
+ this._expanded = !this._props.editable ? !this._expanded : true;
+ })}
+ style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
<SchemaTableCell
Document={this._dashDoc}
col={0}
@@ -187,20 +175,20 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
padding={0}
getFinfo={emptyFunction}
setColumnValues={returnFalse}
- allowCRs={true}
+ allowCRs
oneLine={!this._expanded && !this._props.nodeSelected()}
finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
- autoFocus={true}
+ autoFocus
rootSelected={this._props.tbox._props.rootSelected}
/>
</div>
);
}
- createPivotForField = (e: React.MouseEvent) => {
- let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
+ createPivotForField = () => {
+ const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
@@ -219,7 +207,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleFieldHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey }));
}),
'hideKey'
);
@@ -227,7 +215,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleValueHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue }));
}),
'hideValue'
);
@@ -243,11 +231,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
onPointerDownLabelSpan = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
- DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+ DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey);
const editor = this._props.tbox.EditorView!;
setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
});
@@ -277,7 +265,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}}>
{this._hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+ {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey}
</span>
)}
{this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
@@ -293,65 +281,93 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
);
}
}
-@observer
-export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
- static Instance: DashFieldViewMenu;
- static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
- static toggleFieldHide: () => void = emptyFunction;
- static toggleValueHide: () => void = emptyFunction;
- constructor(props: any) {
- super(props);
- DashFieldViewMenu.Instance = this;
- }
-
- showFields = (e: React.MouseEvent) => {
- DashFieldViewMenu.createFieldView(e);
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleFieldHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleFieldHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleValueHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleValueHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
-
- @observable _fieldKey = '';
+export class DashFieldView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+ tbox: FormattedTextBox;
+ getpos: any;
+ @observable _nodeSelected = false;
+ NodeSelected = () => this._nodeSelected;
- @action
- public show = (x: number, y: number, fieldKey: string) => {
- this._fieldKey = fieldKey;
- this.jumpTo(x, y, true);
- const hideMenu = () => {
- this.fadeOut(true);
- document.removeEventListener('pointerdown', hideMenu, true);
+ unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ makeObservable(this);
+ const self = this;
+ this.node = node;
+ this.tbox = tbox;
+ this.getpos = getPos;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: KeyboardEvent) {
+ e.stopPropagation();
};
- document.addEventListener('pointerdown', hideMenu, true);
- };
- render() {
- return this.getElement(
- <>
- <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.showFields}>
- <FontAwesomeIcon icon="eye" size="sm" />
- </button>
- </Tooltip>
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
- <FontAwesomeIcon icon="bullseye" size="sm" />
- </button>
- </Tooltip>
- )}
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
- <FontAwesomeIcon icon="hashtag" size="sm" />
- </button>
- </Tooltip>
- )}
- </>
+ this.dom.onkeydown = function (e: KeyboardEvent) {
+ e.stopPropagation();
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const editor = tbox.EditorView;
+ if (editor) {
+ const { state } = editor;
+ for (let i = self.getpos() + 1; i < state.doc.content.size; i++) {
+ if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
+ editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
+ return;
+ }
+ }
+ }
+ }
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashFieldViewInternal
+ node={node}
+ unclickable={this.unclickable}
+ getPos={getPos}
+ fieldKey={node.attrs.fieldKey}
+ docId={node.attrs.docId}
+ width={node.attrs.width}
+ height={node.attrs.height}
+ hideKey={node.attrs.hideKey}
+ hideValue={node.attrs.hideValue}
+ editable={node.attrs.editable}
+ nodeSelected={this.NodeSelected}
+ tbox={tbox}
+ />
);
}
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ runInAction(() => {
+ this._nodeSelected = false;
+ });
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ setTimeout(
+ action(() => {
+ this._nodeSelected = true;
+ }),
+ 100
+ );
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index b4102e08e..d9b1a2cf8 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import React, { Component, createRef } from 'react';
// Import JQuery, required for the functioning of the equation editor
@@ -5,11 +6,9 @@ import $ from 'jquery';
import './EquationEditor.scss';
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
window.jQuery = $;
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
require('mathquill/build/mathquill');
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b90653acc..5167c8f2a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
import { TextSelection } from 'prosemirror-state';
@@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor';
import { FormattedTextBox } from './FormattedTextBox';
import { DocData } from '../../../../fields/DocSymbols';
-export class EquationView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- tbox: FormattedTextBox;
- view: any;
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this.tbox = tbox;
- this.view = view;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
- }
- _editor: EquationEditor | undefined;
- setEditor = (editor?: EquationEditor) => (this._editor = editor);
- destroy() {
- this.root.unmount();
- }
- setSelection() {
- this._editor?.mathField.focus();
- }
- selectNode() {
- this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
- setTimeout(() => {
- this._editor?.mathField.focus();
- setTimeout(() => (this.tbox._applyingChange = ''));
- });
- }
- deselectNode() {}
-}
-
interface IEquationViewInternal {
fieldKey: string;
tbox: FormattedTextBox;
@@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = props.tbox.Document;
}
- componentWillUnmount() {
- this._reactionDisposer?.();
- }
componentDidMount() {
this.props.setEditor(this._ref.current ?? undefined);
}
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
render() {
return (
@@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
<EquationEditor
ref={this._ref}
value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
- onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
+ onChange={(str: any) => {
+ this._textBoxDoc[DocData][this._fieldKey] = str;
+ }}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
+ spaceBehavesLikeTab
/>
</div>
);
}
}
+
+export class EquationView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ tbox: FormattedTextBox;
+ view: any;
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this.tbox = tbox;
+ this.view = view;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
+ }
+ _editor: EquationEditor | undefined;
+ setEditor = (editor?: EquationEditor) => {
+ this._editor = editor;
+ };
+ destroy() {
+ this.root.unmount();
+ }
+ setSelection() {
+ this._editor?.mathField.focus();
+ }
+ selectNode() {
+ this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+ setTimeout(() => {
+ this._editor?.mathField.focus();
+ setTimeout(() => {
+ this.tbox._applyingChange = '';
+ });
+ });
+ }
+ deselectNode() {}
+}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index b327e5137..4641da2e9 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -2,9 +2,9 @@ import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { keymap } from 'prosemirror-keymap';
import { baseKeymap, toggleMark } from 'prosemirror-commands';
-import { schema } from './schema_rts';
import { redo, undo } from 'prosemirror-history';
import { StepMap } from 'prosemirror-transform';
+import { schema } from './schema_rts';
export class FootnoteView {
innerView: any;
@@ -100,8 +100,8 @@ export class FootnoteView {
this.innerView.updateState(state);
if (!tr.getMeta('fromOutside')) {
- const outerTr = this.outerView.state.tr,
- offsetMap = StepMap.offset(this.getPos() + 1);
+ const outerTr = this.outerView.state.tr;
+ const offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
@@ -115,7 +115,7 @@ export class FootnoteView {
if (!node.sameMarkup(this.node)) return false;
this.node = node;
if (this.innerView) {
- const state = this.innerView.state;
+ const { state } = this.innerView;
const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 38dd2e847..99b4a84fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -349,8 +349,9 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
- background-color: inherit;
- display: contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
+ // background-color: inherit; // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others
+ display: inline; // needs to be inline for search highlighting to appear
+ // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
}
blockquote {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 43010b2ed..b6d2a9967 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
@@ -12,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -23,14 +25,15 @@ import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -42,18 +45,18 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
import { CollectionTreeView } from '../../collections/CollectionTreeView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
-import { media_state } from '../AudioBox';
+import { styleFromLayoutString, StyleProp } from '../../StyleProvider';
+import { mediaState } from '../AudioBox';
import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { LinkInfo } from '../LinkDocPreview';
-import { PinProps, PresBox } from '../trails';
+import { PresBox } from '../trails';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
import { DashFieldView } from './DashFieldView';
@@ -62,6 +65,7 @@ import { FootnoteView } from './FootnoteView';
import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+// eslint-disable-next-line import/extensions
import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
@@ -79,6 +83,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+ // eslint-disable-next-line no-use-before-define
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
static _globalHighlightsCache: string = '';
@@ -97,7 +102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
- private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -108,10 +112,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
- private _forceDownNode: Node | undefined;
- private _downX = 0;
- private _downY = 0;
- private _downTime = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
public get EditorView() {
@@ -152,10 +152,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
}
@computed get _recordingDictation() {
- return this.dataDoc?.mediaState === media_state.Recording;
+ return this.dataDoc?.mediaState === mediaState.Recording;
}
set _recordingDictation(value) {
- !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this._props);
@@ -170,8 +170,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({
- view(editorView) {
- return new FormattedTextBoxComment(editorView);
+ view(/* editorView */) {
+ return new FormattedTextBoxComment();
},
}),
],
@@ -217,13 +217,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
const state = this._editorView?.state;
- const a1 = linkDoc?.link_anchor_1 as Doc;
- const a2 = linkDoc?.link_anchor_2 as Doc;
+ const a1 = DocCast(linkDoc?.link_anchor_1);
+ const a2 = DocCast(linkDoc?.link_anchor_2);
if (state && a1 && a2 && this._editorView) {
this.removeDocument(a1);
this.removeDocument(a2);
- var allFoundLinkAnchors: any[] = [];
- state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+ let allFoundLinkAnchors: any[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => {
const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
@@ -246,7 +246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+ const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
if (!pinProps && this._editorView?.state.selection.empty) return rootDoc;
const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
this.addDocument(anchor);
@@ -261,11 +261,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
};
- AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnAudio = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
@@ -275,8 +275,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
anchor.followLinkAudio = true;
let stopFunc: any;
const targetData = target[DocData];
- targetData.mediaState = media_state.Recording;
- DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ targetData.mediaState = mediaState.Recording;
+ DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+
const reactionDisposer = reaction(
() => target.mediaState,
dictation => {
@@ -291,7 +292,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -313,7 +314,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
try {
const contents = window.getSelection()?.getRangeAt(0).cloneContents();
if (contents) {
@@ -321,7 +322,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
ele.append(contents);
}
this._selectionHTML = ele?.innerHTML;
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
};
leafText = (node: Node) => {
@@ -330,13 +333,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const fieldKey = StrCast(node.attrs.fieldKey);
return (
(node.attrs.hideKey ? '' : fieldKey + ':') + //
- (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+ (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
);
}
return '';
};
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView && (this._editorView as any).docView) {
+ if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this.tryUpdateDoc(false);
@@ -344,13 +347,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
tryUpdateDoc = (force: boolean) => {
- if (this._editorView && (this._editorView as any).docView) {
- const state = this._editorView.state;
- const dataDoc = this.dataDoc;
+ if (this._editorView) {
+ const { state } = this._editorView;
+ const { dataDoc } = this;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
- const templateData = this.Document !== 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 layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
const effectiveAcl = GetEffectiveAcl(dataDoc);
@@ -359,7 +361,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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) => {
+ 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('#')) {
accumTags.push(node.attrs.fieldKey);
}
@@ -386,7 +388,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else {
// if we've deleted all the text in a note driven by a template, then restore the template data
dataDoc[this.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data)));
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
@@ -414,37 +416,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
insertTime = () => {
let linkTime;
let linkAnchor;
- let link;
- LinkManager.Links(this.dataDoc).forEach((l, i) => {
- const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
+ LinkManager.Links(this.dataDoc).forEach(l => {
+ const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
- link = l;
}
});
if (this._editorView && linkTime) {
- const state = this._editorView.state;
- const now = Date.now();
- let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
- if (!this._break && state.selection.to !== state.selection.from) {
- for (let i = state.selection.from; i <= state.selection.to; i++) {
- const pos = state.doc.resolve(i);
- const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
- if (um) {
- mark = um;
- break;
- }
- }
- }
-
- const path = (this._editorView.state.selection.$from as any).path;
- if (linkAnchor && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) {
+ const { state } = this._editorView;
+ const { path } = state.selection.$from as any;
+ if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) {
const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
this._break = false;
- const from = state.selection.from;
- const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
- const replaced = this._editorView.state.tr.insert(from - 1, value);
+ const { from } = state.selection;
+ const value = state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
+ const replaced = state.tr.insert(from - 1, value);
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
@@ -461,14 +448,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- const f = this._editorView.state.selection.from;
-
- const t = this._editorView.state.selection.to;
- var tr = this._editorView.state.tr as any;
- const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
- tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
- Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ let { tr } = this._editorView.state;
+ const { from, to } = this._editorView.state.selection;
+ const { autoLinkAnchor } = this._editorView.state.schema.marks;
+ tr = tr.removeMark(0, tr.doc.content.size, autoLinkAnchor);
+ Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
+ tr = this.hyperlinkTerm(tr, term, newAutoLinks);
+ });
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
this._editorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -504,11 +491,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
* function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
* document into the code being evaluated.
*/
- hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ let tr = trIn;
const editorView = this._editorView;
- if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
- const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
- var alink: Doc | undefined;
+ if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
+ const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
+ let alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
if (
!sel.$anchor.pos ||
@@ -520,7 +508,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) {
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number /* , parent: any */) => {
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
@@ -551,12 +539,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
};
highlightSearchTerms = (terms: string[], backward: boolean) => {
- if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
- const length = res[0].length;
- let tr = this._editorView.state.tr;
+ const { _editorView } = this;
+ if (_editorView && terms.some(t => t)) {
+ const { state } = _editorView;
+ let { tr } = state;
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const res = terms.filter(t => t).map(term => this.findInNode(_editorView, state.doc, term));
+ const { length } = res[0];
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
@@ -571,23 +561,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
- flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
- flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+ flattened.forEach((h: TextSelection, ind: number) => {
+ tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark);
+ });
+ flattened[lastSel] && _editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
}
};
unhighlightSearchTerms = () => {
- if (window.screen.width < 600) null;
- else if (this._editorView && (this._editorView as any).docView) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const end = this._editorView.state.doc.nodeSize - 2;
- this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ if (this._editorView) {
+ const { state } = this._editorView;
+ if (state) {
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const end = state.doc.nodeSize - 2;
+ this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ }
}
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
- const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+ const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
};
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -631,7 +625,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
float: 'unset',
});
if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
- added = dragData.removeDocument?.(draggedDoc) ? true : false;
+ added = !!dragData.removeDocument?.(draggedDoc);
} else {
added = true;
}
@@ -643,9 +637,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._inDrop = true;
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
pos && view.dispatch(view.state.tr.insert(pos, node));
- added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
- } catch (e) {
- console.log('Drop failed', e);
+ added = !!pos; // pos will be null if you don't drop onto an actual text location
+ } catch (err) {
+ console.log('Drop failed', err);
added = false;
} finally {
this._inDrop = false;
@@ -677,29 +671,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
offset += (context.content as any).content[i].nodeSize;
}
- return null;
- } else {
- return null;
}
+ return null;
}
- //Recursively finds matches within a given node
+ // Recursively finds matches within a given node
findInNode(pm: EditorView, node: Node, find: string) {
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0,
- foundAt;
+ let index = 0;
+ let foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
const regexp = new RegExp(find, 'i');
if (regexp) {
- var blockOffset = 0;
- for (var i = 0; i < node.childCount; i++) {
- var textContent = '';
+ let blockOffset = 0;
+ for (let i = 0; i < node.childCount; i++) {
+ let textContent = '';
while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
textContent += node.child(i).textContent;
i++;
}
+ // eslint-disable-next-line no-cond-assign
while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
ret.push(sel);
@@ -710,14 +703,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
} else {
- node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
+ node.content.forEach(child => {
+ ret = ret.concat(this.findInNode(pm, child, find));
+ });
}
return ret;
}
updateHighlights = (highlights: string[]) => {
if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
- setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
+ setTimeout(() => {
+ FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
+ });
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
if (!highlights.includes('Audio Tags')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
@@ -726,7 +723,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
}
if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -745,21 +742,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
}
if (highlights.includes('By Recent Minute')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
if (highlights.includes('By Recent Hour')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
+ // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@observable _showSidebar = false;
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
}
@action
@@ -781,7 +779,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this,
e,
this.sidebarMove,
- (e, movement, isClick) => !isClick && batch.end(),
+ (moveEv, movement, isClick) => !isClick && batch.end(),
() => {
this.toggleSidebar();
batch.end();
@@ -817,7 +815,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
@undoBatch
- makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
+ makeTargetToggle = (anchor: Doc) => {
+ anchor.followLinkToggle = !anchor.followLinkToggle;
+ };
@undoBatch
showTargetTrail = (anchor: Doc) => {
@@ -833,11 +833,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const editor = this._editorView!;
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- if (target && !(e.nativeEvent as any).dash) {
+ const editor = this._editorView;
+ if (editor && target && !(e.nativeEvent as any).dash) {
const hrefs = (target.dataset?.targethrefs as string)
?.trim()
.split(' ')
@@ -847,8 +846,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
.replace(Doc.localServerPath(), '')
.split('?')[0];
const deleteMarkups = undoBatch(() => {
- const sel = editor.state.selection;
- editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor));
+ const { selection } = editor.state;
+ editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor));
});
e.persist();
anchorDoc &&
@@ -884,7 +883,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+ setTimeout(() => {
+ this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
+ }, 50);
}),
icon: 'eye',
});
@@ -923,19 +924,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
appearanceItems.push({
description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
- event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar),
+ event: () => {
+ this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar;
+ },
icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
});
appearanceItems.push({
description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
- event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
+ event: () => {
+ this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI;
+ },
icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
});
!Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Broadcast Message',
- event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text)),
+ event: () =>
+ DocServer.GetRefField('rtfProto').then(proto => {
+ proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text);
+ }),
icon: 'expand-arrows-alt',
});
@@ -957,19 +965,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
+ optionItems.push({
+ description: `Toggle auto update from template`,
+ event: () => {
+ this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate'];
+ },
+ icon: 'star',
+ });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
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),
+ 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`,
- event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
+ event: () => {
+ this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+ },
icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
});
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
@@ -977,7 +995,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const helpItems = help && 'subitems' in help ? help.subitems : [];
helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
- this._downX = this._downY = Number.NaN;
};
animateRes = (resIndex: number, newText: string) => {
@@ -990,7 +1007,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
askGPT = action(async () => {
try {
- let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
this.animateRes(0, 'Something went wrong.');
} else if (this._editorView) {
@@ -1018,8 +1035,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._editorView && this._recordingDictation) {
this.stopDictation(true);
this._break = true;
- const state = this._editorView.state;
- const to = state.selection.to;
+ const { state } = this._editorView;
+ const { to } = state.selection;
const updated = TextSelection.create(state.doc, to, to);
this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
if (this._recordingDictation) {
@@ -1037,7 +1054,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
};
- stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
+ stopDictation = (abort: boolean) => DictationManager.Controls.stop(/* !abort */);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
@@ -1065,7 +1082,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
}
- const from = this._editorView.state.selection.from;
+ const { from } = this._editorView.state.selection;
this._break = false;
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
@@ -1074,23 +1091,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) {
- const state = this._editorView?.state;
- if (state) {
+ const { _editorView } = this;
+ if (_editorView) {
+ const { state } = _editorView;
let selectedText = '';
- const sel = state.selection;
+ const { selection } = state;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
- let tr = state.tr.addMark(sel.from, sel.to, splitter);
- if (sel.from !== sel.to) {
+ let tr = state.tr.addMark(selection.from, selection.to, splitter);
+ if (selection.from !== selection.to) {
const anchor =
anchorDoc ??
Docs.Create.ConfigDocument({
//
- title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
+ title: 'text(' + state.doc.textBetween(selection.from, selection.to) + ')',
annotationOn: this.dataDoc,
});
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ tr.doc.nodesBetween(selection.from, selection.to, (node: any, pos: number /* , parent: any */) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
const allAnchors = [{ href, title, anchorId: anchor[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1100,7 +1118,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
- this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+ this._editorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
anchor.text = selectedText;
anchor.text_html = this._selectionHTML ?? selectedText;
@@ -1121,15 +1139,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+ });
};
focus = (textAnchor: Doc, options: FocusViewOptions) => {
const focusSpeed = options.zoomTime ?? 500;
const textAnchorId = textAnchor[Id];
+ let start = 0;
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
+ // eslint-disable-next-line no-use-before-define
const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
nodes.push(examinedNode.node);
@@ -1161,7 +1183,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
- let start = 0;
this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
if (this._editorView && textAnchorId) {
const editor = this._editorView;
@@ -1177,13 +1198,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
- setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
+ setTimeout(() => {
+ this._focusSpeed = undefined;
+ }, this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
return focusSpeed;
- } else {
- return this._props.focus(this.Document, options);
}
+ return this._props.focus(this.Document, options);
}
+ return undefined;
};
// if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
@@ -1203,7 +1226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
() => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
- (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
+ autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
this._disposers.highlights = reaction(
() => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1212,21 +1235,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.width = reaction(
() => this._props.PanelWidth(),
- width => this.tryUpdateScrollHeight()
+ () => this.tryUpdateScrollHeight()
);
this._disposers.scrollHeight = reaction(
- () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
- ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight),
+ () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
+ ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
{ fireImmediately: true }
);
this._disposers.componentHeights = reaction(
// set the document height when one of the component heights changes and layout_autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
- ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
+ ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
(!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
- layout_autoHeight &&
+ layoutAutoHeight &&
newHeight &&
newHeight !== this.layoutDoc.height &&
!this._props.dontRegisterView
@@ -1274,14 +1297,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.search = reaction(
() => Doc.IsSearchMatch(this.Document),
search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false }
+ { fireImmediately: !!Doc.IsSearchMatchUnmemoized(this.Document) }
);
this._disposers.selected = reaction(
() => this._props.rootSelected?.(),
action(selected => {
- //selected && setTimeout(() => this.prepareForTyping());
+ this.prepareForTyping();
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ // eslint-disable-next-line operator-assignment
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
}
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1322,10 +1346,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
clipboardTextSerializer = (slice: Slice): string => {
- let text = '',
- separated = true;
- const from = 0,
- to = slice.content.size;
+ let text = '';
+ let separated = true;
+ const from = 0;
+ const to = slice.content.size;
slice.content.nodesBetween(
from,
to,
@@ -1345,9 +1369,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return text;
};
- handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+ handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
- return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
+ return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
};
addPdfReference = (pdfAnchorId: string) => {
@@ -1378,7 +1402,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return false;
};
- isActiveTab(el: Element | null | undefined) {
+ isActiveTab(elIn: Element | null | undefined) {
+ let el = elIn;
while (el && el !== document.body) {
if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
@@ -1390,7 +1415,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const self = this;
return new Plugin({
view(newView) {
- runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+ runInAction(() => {
+ self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ });
return new RichTextMenuPlugin({ editorProps: this._props });
},
});
@@ -1415,7 +1442,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
- setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
+ setTimeout(() => {
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ });
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1425,25 +1454,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- dashComment(node: any, view: any, getPos: any) {
- return new DashDocCommentView(node, view, getPos);
- },
- dashDoc(node: any, view: any, getPos: any) {
- return new DashDocView(node, view, getPos, self);
- },
- dashField(node: any, view: any, getPos: any) {
- return new DashFieldView(node, view, getPos, self);
- },
- equation(node: any, view: any, getPos: any) {
- return new EquationView(node, view, getPos, self);
- },
- summary(node: any, view: any, getPos: any) {
- return new SummaryView(node, view, getPos);
- },
- //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
- footnote(node: any, view: any, getPos: any) {
- return new FootnoteView(node, view, getPos);
- },
+ dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore
+ dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, // prettier-ignore
+ dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore
+ equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, // prettier-ignore
+ summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, // prettier-ignore
+ footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }, // prettier-ignore
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1451,7 +1467,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const { state, dispatch } = this._editorView;
if (!rtfField) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
- const startupText = Field.toString(dataDoc[fieldKey] as Field);
+ const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
@@ -1475,7 +1491,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._props.select(false);
if (selLoadChar) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
- const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
@@ -1483,8 +1499,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const tr = tr2.setStoredMarks(storedMarks);
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);
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else if (!FormattedTextBox.DontSelectInitialText) {
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ selectAll(this._editorView.state, (tx: Transaction) => {
+ this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+ });
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else {
+ const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+ const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
+ const { tr } = this._editorView.state;
+ this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
}
}
@@ -1503,17 +1531,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// 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.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, 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));
+ if (this._editorView) {
+ const { text, paragraph } = schema.nodes;
+ const selNode = this._editorView.state.selection.$anchor.node();
+ if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
+ this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+ }
+ }
};
componentWillUnmount() {
@@ -1532,8 +1557,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onPointerDown = (e: React.PointerEvent): void => {
if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return; //e.stopPropagation();
- } else (e.nativeEvent as any).handledByInnerReactInstance = true;
+ return; // e.stopPropagation();
+ }
+ (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different embedContainer (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
@@ -1559,9 +1585,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
- this._downX = e.clientX;
- this._downY = e.clientY;
- this._downTime = Date.now();
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1575,17 +1598,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.preventDefault();
}
};
- onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener('pointerup', this.onSelectEnd);
- };
+ onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd);
onPointerUp = (e: React.PointerEvent): void => {
const state = this.EditorView?.state;
if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
}
};
@action
@@ -1613,7 +1634,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
@action
onFocused = (e: React.FocusEvent): void => {
- //applyDevTools.applyDevTools(this._editorView);
+ // applyDevTools.applyDevTools(this._editorView);
this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
@@ -1624,10 +1645,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
return;
}
- if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
- this._forceDownNode = undefined;
- return;
- }
if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
// this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1651,13 +1668,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.rootSelected?.()) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
- this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
+ this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
- this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
};
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+ hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
@@ -1710,9 +1726,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
this.autoLink();
if (this._editorView?.state.tr) {
- const tr = stordMarks?.reduce((tr, m) => {
- tr.addStoredMark(m);
- return tr;
+ const tr = stordMarks?.reduce((tr2, m) => {
+ tr2.addStoredMark(m);
+ return tr2;
}, this._editorView.state.tr);
tr && this._editorView.dispatch(tr);
}
@@ -1727,6 +1743,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
if (match) {
this.dataDoc.title_custom = true;
+ // eslint-disable-next-line prefer-destructuring
this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
}
@@ -1736,33 +1753,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
- const state = this._editorView!.state;
// if the text box blurs and none of its contents are focused(), then pass the blur along
setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
};
onKeyDown = (e: React.KeyboardEvent) => {
+ const { _editorView } = this;
+ if (!_editorView) return;
if ((e.altKey || e.ctrlKey) && e.key === 't') {
- e.preventDefault();
- e.stopPropagation();
this._props.setTitleFocus?.();
+ StopEvent(e);
return;
}
- const state = this._editorView!.state;
+ const { state } = _editorView;
if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
- e.preventDefault();
- e.stopPropagation();
+ StopEvent(e);
return;
}
if (state.selection.empty || !this._rules!.EnteringStyle) {
this._rules!.EnteringStyle = false;
}
- let stopPropagation = true;
- for (var i = state.selection.from; i <= state.selection.to; i++) {
+ for (let i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail()) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
e.preventDefault();
}
}
@@ -1775,22 +1790,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return;
case 'Enter':
this.insertTime();
+ // eslint-disable-next-line no-fallthrough
case 'Tab':
e.preventDefault();
break;
- case 'c':
- this._editorView?.state.selection.empty && (stopPropagation = false);
+ case 'Space':
+ case 'Backspace':
break;
default:
- if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case ' ':
- if (e.code !== 'Space' && e.code !== 'Backspace') {
- [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
- 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) })));
+ if ([AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document))) {
+ const modified = Math.floor(Date.now() / 1000);
+ const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark && m.attrs.modified === modified);
+ _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified })));
}
break;
}
- if (stopPropagation) e.stopPropagation();
+ e.stopPropagation();
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
@@ -1810,6 +1825,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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 && !SnappingManager.IsDragging) {
+ // eslint-disable-next-line no-use-before-define
const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
const toNum = (val: string) => Number(val.replace('px', ''));
const toHgt = (node: Element): number => {
@@ -1821,7 +1837,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const scrollHeight = this.ProseRef && proseHeight;
if (this._props.setHeight && !this._props.suppressSetHeight && 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.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
+ const setScrollHeight = () => {
+ this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+ };
if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
@@ -1839,7 +1857,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
+ setSidebarHeight = (height: number) => {
+ this.dataDoc[this.SidebarKey + '_height'] = height;
+ };
sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
this._props
@@ -1857,10 +1877,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e,
returnFalse,
emptyFunction,
- action(e => (this._recordingDictation = !this._recordingDictation))
+ action(() => {
+ this._recordingDictation = !this._recordingDictation;
+ })
)
}>
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon="microphone" size="sm" />
</div>
);
}
@@ -1889,11 +1911,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
showSidebar={this.SidebarShown}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -1908,6 +1931,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
@@ -1930,8 +1954,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
renderDepth={this._props.renderDepth + 1}
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
- noSidebar={true}
- treeViewHideTitle={true}
+ noSidebar
+ treeViewHideTitle
fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
@@ -1969,7 +1993,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}>
<div
className="formattedTextBox-alternateButton"
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
+ onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.cycleAlternateText())}
style={{
display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -2001,12 +2025,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
if (this._props.isContentActive()) {
const scale = this._props.NativeDimScaling?.() || 1;
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- const height = Number(styleFromLayoutString.height?.replace('px', ''));
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ const height = Number(styleFromLayout.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- !Number.isNaN(height) &&
- (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ !isNaN(height) &&
+ (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height || this._props.PanelHeight()) / scale) && //
(this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
@@ -2016,7 +2040,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
_oldWheel: any;
@computed get fontColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
}
@computed get fontSize() {
return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
@@ -2034,15 +2058,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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 styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- return styleFromLayoutString?.height === '0px' ? null : (
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ return styleFromLayout?.height === '0px' ? null : (
<div
className="formattedTextBox"
onPointerEnter={action(() => {
this._isHovering = true;
this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
})}
- onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
+ onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore
ref={r => {
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = r;
@@ -2062,7 +2086,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fontSize: this.fontSize,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
- ...styleFromLayoutString,
+ ...styleFromLayout,
}}>
<div
className="formattedTextBox-cont"
@@ -2084,7 +2108,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onDoubleClick={this.onDoubleClick}>
<div
className="formattedTextBox-outer"
- ref={r => (this._scrollRef = r)}
+ ref={r => {
+ this._scrollRef = r;
+ }}
style={{
width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index ce17af6ca..01c46edeb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,15 +1,16 @@
import { Mark, ResolvedPos } from 'prosemirror-model';
-import { EditorState, NodeSelection } from 'prosemirror-state';
+import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
-import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
import { schema } from './schema_rts';
export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail());
}
export function findUserMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.attrs.userid);
@@ -18,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
}
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let before = 0,
- nbef = rpos.nodeBefore;
+ let before = 0;
+ let nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
rpos && (nbef = rpos.nodeBefore);
}
return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let after = 0,
- naft = rpos.nodeAfter;
+ let after = 0;
+ let naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
rpos && (naft = rpos.nodeAfter);
}
@@ -49,7 +52,7 @@ export class FormattedTextBoxComment {
static userMark: Mark;
static textBox: FormattedTextBox | undefined;
- constructor(view: any) {
+ constructor() {
if (!FormattedTextBoxComment.tooltip) {
const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
@@ -79,10 +82,10 @@ export class FormattedTextBoxComment {
}
static showCommentbox(view: EditorView, nbef: number) {
- const state = view.state;
+ const { state } = view;
// These are in screen coordinates
- const start = view.coordsAtPos(state.selection.from - nbef),
- end = view.coordsAtPos(state.selection.from - nbef);
+ const start = view.coordsAtPos(state.selection.from - nbef);
+ const end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
@@ -109,14 +112,16 @@ export class FormattedTextBoxComment {
}
static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
- const state = view.state;
+ const { state } = view;
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
const noselection = state.selection.$from === state.selection.$to;
let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+ !child && node.marks.length && (child = node);
+ });
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
@@ -131,7 +136,7 @@ export class FormattedTextBoxComment {
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- //nbef &&
+ // nbef &&
naft &&
LinkInfo.SetLinkInfo({
DocumentView: textBox.DocumentView,
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
index c3595e59b..dbc60f7bf 100644
--- a/src/client/views/nodes/formattedText/OrderedListView.tsx
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -1,8 +1,7 @@
export class OrderedListView {
-
- update(node: any) {
- // if attr's of an ordered_list (e.g., bulletStyle) change,
+ update() {
+ // if attr's of an ordered_list (e.g., bulletStyle) change,
// return false forces the dom node to be recreated which is necessary for the bullet labels to update
- return false;
+ return false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 30da91710..6c88a0d29 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,19 @@
+/* eslint-disable import/extensions */
+import { Node, DOMOutputSpec } from 'prosemirror-model';
import clamp from '../../../util/clamp';
import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
-//import type { NodeSpec } from './Types';
+// import type { NodeSpec } from './Types';
type NodeSpec = {
- attrs?: { [key: string]: any },
- content?: string,
- draggable?: boolean,
- group?: string,
- inline?: boolean,
- name?: string,
- parseDOM?: Array<any>,
- toDOM?: (node: any) => DOMOutputSpec,
+ attrs?: { [key: string]: any };
+ content?: string;
+ draggable?: boolean;
+ group?: string;
+ inline?: boolean;
+ name?: string;
+ parseDOM?: Array<any>;
+ toDOM?: (node: any) => DOMOutputSpec;
};
// This assumes that every 36pt maps to one indent level.
@@ -25,41 +26,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
const ALIGN_PATTERN = /(left|right|center|justify)/;
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-export const ParagraphNodeSpec: NodeSpec = {
- attrs: {
- align: { default: null },
- color: { default: null },
- id: { default: null },
- indent: { default: null },
- inset: { default: null },
- lineSpacing: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingBottom: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingTop: { default: null },
- },
- content: 'inline*',
- group: 'block',
- parseDOM: [{ tag: 'p', getAttrs }],
- toDOM,
-};
+function convertMarginLeftToIndentValue(marginLeft: string): number {
+ const ptValue = convertToCSSPTValue(marginLeft);
+ return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
+}
function getAttrs(dom: HTMLElement): Object {
- const {
- lineHeight,
- textAlign,
- marginLeft,
- paddingTop,
- paddingBottom,
- } = dom.style;
+ const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
let align = dom.getAttribute('align') || textAlign || '';
- align = ALIGN_PATTERN.test(align) ? align : "";
+ align = ALIGN_PATTERN.test(align) ? align : '';
- let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+ let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
if (!indent && marginLeft) {
indent = convertMarginLeftToIndentValue(marginLeft);
@@ -74,15 +52,7 @@ function getAttrs(dom: HTMLElement): Object {
}
function toDOM(node: Node): DOMOutputSpec {
- const {
- align,
- indent,
- inset,
- lineSpacing,
- paddingTop,
- paddingBottom,
- id,
- } = node.attrs;
+ const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
const attrs: { [key: string]: any } | null = {};
let style = '';
@@ -128,16 +98,29 @@ function toDOM(node: Node): DOMOutputSpec {
return ['p', attrs, 0];
}
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+export const ParagraphNodeSpec: NodeSpec = {
+ attrs: {
+ align: { default: null },
+ color: { default: null },
+ id: { default: null },
+ indent: { default: null },
+ inset: { default: null },
+ lineSpacing: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingBottom: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingTop: { default: null },
+ },
+ content: 'inline*',
+ group: 'block',
+ parseDOM: [{ tag: 'p', getAttrs }],
+ toDOM,
+};
+
export const toParagraphDOM = toDOM;
export const getParagraphNodeAttrs = getAttrs;
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
- const ptValue = convertToCSSPTValue(marginLeft);
- return clamp(
- MIN_INDENT_LEVEL,
- Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
- MAX_INDENT_LEVEL
- );
-}
-
-export default ParagraphNodeSpec; \ No newline at end of file
+export default ParagraphNodeSpec;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 03c902580..5d448d40e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,29 +1,29 @@
import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
+import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
-import { GetEffectiveAcl } from '../../../../fields/util';
+import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
+import { GetEffectiveAcl } from '../../../../fields/util';
import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
-import { Doc } from '../../../../fields/Doc';
-import { EditorView } from 'prosemirror-view';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
-export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
let mapStyle = assignedMapStyle;
- tx2.doc.descendants((node: any, offset: any, index: any) => {
+ tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
- const path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ const { path } = tx2.doc.resolve(offset) as any;
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
@@ -34,38 +34,44 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
return tx2;
};
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
const keys: { [key: string]: any } = {};
function bind(key: string, cmd: any) {
- if (mapKeys) {
- const mapped = mapKeys[key];
- if (mapped === false) return;
- if (mapped) key = mapped;
- }
keys[key] = cmd;
}
+ function onKey(): boolean | undefined {
+ // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
+ // eslint-disable-next-line no-restricted-globals
+ return props.onKey?.(event, props);
+ }
+
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.TemplateDataDocument)) {
+ const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+ switch (permissions) {
case AclAugment:
- 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;
+ {
+ const prevNode = state.selection.$cursor.nodeBefore;
+ const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
+ if (prevUser !== ClientUtils.CurrentUserEmail()) {
+ return false;
+ }
}
+ break;
+ default:
}
return true;
};
const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
- //History commands
+ // History commands
bind('Mod-z', undo);
bind('Shift-Mod-z', redo);
!mac && bind('Mod-y', redo);
- //Commands to modify Mark
+ // Commands to modify Mark
bind('Mod-b', toggleEditableMark(schema.marks.strong));
bind('Mod-B', toggleEditableMark(schema.marks.strong));
@@ -77,15 +83,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Mod-u', toggleEditableMark(schema.marks.underline));
bind('Mod-U', toggleEditableMark(schema.marks.underline));
- //Commands for lists
+ // Commands for lists
bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
- bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Tab', () => onKey() || true);
+ bind('Alt-Tab', () => onKey() || true);
+ bind('Meta-Tab', () => onKey() || true);
+ bind('Meta-Enter', () => onKey() || true);
bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
@@ -103,8 +109,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
const tx25 = updateBullets(tx2, schema);
- const ol_node = tx25.doc.nodeAt(range!.start)!;
- const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
+ const olNode = tx25.doc.nodeAt(range!.start)!;
+ const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -115,10 +121,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
console.log('bullet promote fail');
}
}
+ return undefined;
});
bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -132,15 +139,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
) {
console.log('bullet demote fail');
}
+ return undefined;
});
- //Command to create a new Tab with a PDF of all the command shortcuts
- bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+ // Command to create a new Tab with a PDF of all the command shortcuts
+ bind('Mod-/', () => {
+ const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
props.addDocTab(newDoc, OpenWhere.addRight);
});
- //Commands to modify BlockType
+ // Commands to modify BlockType
bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
@@ -156,24 +164,24 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
}
- //Command to create a horizontal break line
+ // Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
- //Command to unselect all
+ // Command to unselect all
bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
});
- bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
- bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Alt-Enter', () => onKey() || true);
+ bind('Ctrl-Enter', () => onKey() || true);
bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
- bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Cmd-?', () => {
RTFMarkup.Instance.open();
return true;
});
@@ -189,14 +197,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -204,14 +212,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -219,14 +227,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -236,7 +244,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
- const tr = state.tr;
+ const { tr } = state;
tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
dispatch(
tr.setSelection(
@@ -258,7 +266,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
if (
@@ -271,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
dispatch(updateBullets(tx, schema));
if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
// gets rid of an extra paragraph when joining two list items together.
- joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+ joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
}
})
) {
@@ -288,11 +296,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Backspace', backspace);
- //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
- //command to break line
+ // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
+ // command to break line
const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -337,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
!splitBlockKeepMarks(state, (tx3: Transaction) => {
const tonode = tx3.selection.$to.node();
if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
dispatch(tx4);
}
@@ -356,9 +364,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Enter', enter);
- //Command to create a blank space
- bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
+ // Command to create a blank space
+ bind('Space', () => {
+ const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+ if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
return false;
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cecf106a3..6c12b9991 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -3,14 +3,13 @@ import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -23,10 +22,12 @@ import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
+
const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
static get Instance() {
return RichTextMenu._instance?.menu;
@@ -116,7 +117,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
() => SelectionManager.Views.slice(),
- views => this.updateMenu(undefined, undefined, undefined, undefined)
+ () => this.updateMenu(undefined, undefined, undefined, undefined)
);
}
componentWillUnmount() {
@@ -139,18 +140,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
- const activeFamilies = active.activeFamilies;
- const activeSizes = active.activeSizes;
- const activeColors = active.activeColors;
- const activeHighlights = active.activeHighlights;
+ const { activeFamilies } = active;
+ const { activeSizes } = active;
+ const { activeColors } = active;
+ const { activeHighlights } = active;
const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+ const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -159,12 +161,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
- const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
- const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
- const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const fromRange = numberRange(state.selection.from).reverse();
- const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+ const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
const hasMark = node.marks.some(m => m.type === mark.type);
@@ -172,25 +169,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
dispatch(updateBullets(markup, state.schema));
- } else {
- const state = this.view?.state;
- const tr = this.view?.state.tr;
- if (tr && state) {
- if (dontToggle) {
- tr.addMark(state.selection.from, state.selection.to, mark);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
- } else {
- toggleMark(mark.type, mark.attrs)(state, dispatch);
- }
+ } else if (state) {
+ const { tr } = state;
+ if (dontToggle) {
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
+ this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
// finds font sizes and families in selection
getActiveAlignment() {
if (this.view && this.TextView?._props.rootSelected?.()) {
- const path = (this.view.state.selection.$from as any).path;
+ const { path } = this.view.state.selection.$from as any;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
return path[i].attrs.align || 'left';
@@ -222,24 +217,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeColors = new Set<string>();
const activeHighlights = new Set<string>();
if (this.view && this.TextView?._props.rootSelected?.()) {
- const state = this.view.state;
+ const { state } = this.view;
const pos = this.view.state.selection.$from;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
marks.forEach(m => {
- m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family);
- m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color);
+ m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
+ m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
- m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
+ m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
});
} else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
@@ -254,26 +250,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return found;
}
- //finds all active marks on selection in given group
+ // finds all active marks on selection in given group
getActiveMarksOnSelection() {
if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const state = this.view.state;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const { state } = this.view;
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
const pos = this.view.state.selection.$from;
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- return markGroup.filter(mark_type => {
- const mark = state.schema.mark(mark_type);
+ return markGroup.filter(markType => {
+ const mark = state.schema.mark(markType);
return mark.isInSet(marks);
});
}
@@ -291,7 +288,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._superscriptActive = false;
activeMarks.forEach(mark => {
- // prettier-ignore
switch (mark.name) {
case 'noAutoLinkAnchor': this._noLinkActive = true; break;
case 'strong': this._boldActive = true; break;
@@ -300,7 +296,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
case 'strikethrough': this._strikethroughActive = true; break;
case 'subscript': this._subscriptActive = true; break;
case 'superscript': this._superscriptActive = true; break;
- }
+ default:
+ } // prettier-ignore
});
}
@@ -353,53 +350,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- setFontSize = (fontSize: string) => {
+ setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.view) {
- if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
- this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
- this.view.focus();
- } else {
- const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ const { text, paragraph } = this.view.state.schema.nodes;
+ const selNode = this.view.state.selection.$anchor.node();
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
this.view.focus();
}
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
- } else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- };
-
- setFontFamily = (family: string) => {
- if (this.view) {
- const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
+ const attrs: { [key: string]: string } = {};
+ attrs[fontField] = value;
+ const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
- } else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ } else {
+ Doc.UserDoc()[fontField] = value;
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ }
};
- setHighlight(color: string) {
- if (this.view) {
- const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
- this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
- this.view.focus();
- } else Doc.UserDoc()._fontHighlight = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
- setColor(color: string) {
- if (this.view) {
- const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
- this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
- this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
- } else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
@@ -407,7 +376,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const newMapStyle = active === mapStyle ? '' : mapStyle;
if (!this.view || newMapStyle === '') return;
- let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
+ const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
if (inList) {
const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
@@ -428,7 +397,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
- const tr = state.tr;
+ const { tr } = state;
tr.addMark(state.selection.from, state.selection.to, mark);
const content = tr.selection.content();
const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
@@ -436,13 +405,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- vcenterToggle = (view: EditorView, dispatch: any) => {
+ vcenterToggle = () => {
this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
- var tr = view.state.tr;
- view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+ let { tr } = view.state;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
return false;
@@ -455,56 +424,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- insetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
- outsetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
-
- indentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- const heading = false;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
- return false;
- }
- return true;
- });
- !heading && dispatch?.(tr);
- return true;
- }
-
- hangingIndentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+ let { tr } = state;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ const newValue = !value ?
+ (node.attrs[field] ? 0 : node.attrs[field] + 10) :
+ Math.max(0, value); // prettier-ignore
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
return false;
}
return true;
@@ -514,7 +441,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
insertBlockquote(state: EditorState, dispatch: any) {
- const path = (state.selection.$from as any).path;
+ const { path } = state.selection.$from as any;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
} else {
@@ -547,13 +474,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState, dispatch: any) {
+ fillBrush() {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (selected_marks.size >= 0) {
- this.brushMarks = selected_marks;
+ const selectedMarks = this.getMarksInSelection(this.view.state);
+ if (selectedMarks.size >= 0) {
+ this.brushMarks = selectedMarks;
}
} else {
const { from, to, $from } = this.view.state.selection;
@@ -597,9 +524,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ }
</Tooltip>
);
@@ -607,21 +537,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
Apply hyperlink
</button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
Remove link
</button>
</div>
);
- return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ // eslint-disable-next-line no-use-before-define
+ return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
}
async getTextLinkTargetTitle() {
- if (!this.view) return;
+ if (!this.view) return undefined;
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === 'link');
@@ -633,15 +564,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
- const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
- const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
+ const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
+ const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
const currentDoc = SelectionManager.Docs.lastElement();
- if (currentDoc && link_anchor_1 && link_anchor_2) {
- if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) {
- return StrCast(link_anchor_2.title);
+ if (currentDoc && linkAnchor1 && linkAnchor2) {
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
+ return StrCast(linkAnchor2.title);
}
- if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
- return StrCast(link_anchor_1.title);
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
+ return StrCast(linkAnchor1.title);
}
}
}
@@ -653,11 +584,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return link.attrs.title;
}
}
+ return undefined;
}
// TODO: should check for valid URL
@undoBatch
- makeLinkToURL = (target: string, lcoation: string) => {
+ makeLinkToURL = (target: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
};
@@ -736,7 +668,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ <div
+ className="button-dropdown-wrapper"
+ ref={node => {
+ this.ref = node;
+ }}>
{!this._props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this._props.button}
@@ -747,9 +683,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
) : (
<>
{this._props.button}
- <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </button>
+ }
</>
)}
{this.showDropdown ? this._props.dropdownContent : null}
@@ -762,10 +701,11 @@ interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() {
- return null;
- }
+ // eslint-disable-next-line react/no-unused-class-component-methods
update(view: EditorView, lastState: EditorState | undefined) {
RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
}
+ render() {
+ return null;
+ }
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 42665830f..c107a6724 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,5 +1,6 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -13,6 +14,7 @@ import { CollectionView } from '../../collections/CollectionView';
import { ContextMenu } from '../../ContextMenu';
import { KeyValueBox } from '../KeyValueBox';
import { FormattedTextBox } from './FormattedTextBox';
+// eslint-disable-next-line import/extensions
import { wrappingInputRule } from './prosemirrorPatches';
import { RichTextMenu } from './RichTextMenu';
import { schema } from './schema_rts';
@@ -48,13 +50,9 @@ export class RichTextRules {
/^A\.\s$/,
schema.nodes.ordered_list,
// match => {
- () => {
- return { mapStyle: 'multi', bulletStyle: 1 };
- // return ({ order: +match[1] })
- },
- (match: any, node: any) => {
- return node.childCount + node.attrs.order === +match[1];
- },
+ () => ({ mapStyle: 'multi', bulletStyle: 1 }),
+ // return ({ order: +match[1] })
+ (match: any, node: any) => node.childCount + node.attrs.order === +match[1],
((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
@@ -70,7 +68,7 @@ export class RichTextRules {
// ``` create code block
new InputRule(/^```$/, (state, match, start, end) => {
- let $start = state.doc.resolve(start);
+ const $start = state.doc.resolve(start);
if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
// this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
@@ -86,13 +84,13 @@ export class RichTextRules {
}),
// %<font-size> set the font size
- new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+ new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => {
const size = Number(match[1]);
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>::$/, (state, match, start, end) => {
const creator = (doc: Doc) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -107,7 +105,7 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 2, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: this.TextBox.EditorView.state.tr
);
};
@@ -117,8 +115,8 @@ export class RichTextRules {
return null;
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>>$/, (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
textDoc.inlineTextCount = numInlines + 1;
@@ -150,13 +148,13 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 1, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: state.tr;
return replaced;
}),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+ new InputRule(/(%d|d)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -171,7 +169,7 @@ export class RichTextRules {
}),
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+ new InputRule(/(%h|h)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -186,11 +184,11 @@ export class RichTextRules {
}),
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+ new InputRule(/(%q|q)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
- const node = state.selection.node;
+ const { node } = state.selection;
return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
}
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -205,46 +203,43 @@ export class RichTextRules {
}),
// center justify text
- new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+ new InputRule(/%\^/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// left justify text
- new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+ new InputRule(/%\[/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// right justify text
- new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+ new InputRule(/%\]/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// activate a style by name using prefix '%<color name>'
- new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
+ new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
const marks = RichTextMenu.Instance?._brushMap.get(color);
@@ -259,7 +254,7 @@ export class RichTextRules {
}
if (marks) {
const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr;
}
const isValidColor = (strColor: string) => {
@@ -269,33 +264,33 @@ export class RichTextRules {
};
if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ fontColor: color }));
}
return null;
}),
// toggle alternate text UI %/
- new InputRule(new RegExp(/%\//), (state, match, start, end) => {
+ new InputRule(/%\//, (state, match, start, end) => {
setTimeout(() => this.TextBox.cycleAlternateText(true));
return state.tr.deleteRange(start, end);
}),
// stop using active style
- new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+ new InputRule(/%%$/, (state, match, start, end) => {
const tr = state.tr.deleteRange(start, end);
const marks = state.tr.selection.$anchor.nodeBefore?.marks;
return marks
? Array.from(marks)
.filter(m => m.type !== state.schema.marks.user_mark)
- .reduce((tr, m) => tr.removeStoredMark(m), tr)
+ .reduce((tr2, m) => tr2.removeStoredMark(m), tr)
: tr;
}),
// create a hyperlink to a titled document
// @(<doctitle>)
- new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+ new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => {
const docTitle = match[1];
const prefixLength = '@('.length;
if (docTitle) {
@@ -315,11 +310,11 @@ export class RichTextRules {
teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
}
};
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
+ const getTitledDoc = (title: string) => {
+ if (!DocServer.FindDocByTitle(title)) {
+ Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
}
- const titledDoc = DocServer.FindDocByTitle(docTitle);
+ const titledDoc = DocServer.FindDocByTitle(title);
return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
};
const target = getTitledDoc(docTitle);
@@ -335,22 +330,31 @@ export class RichTextRules {
// [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
// [@{this,doctitle,}.fieldKey]
new InputRule(
- new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+ /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/,
(state, match, start, end) => {
const docTitle = match[1].substring(1).replace(/\.$/, '');
const fieldKey = match[2];
const assign = match[4] === ':' ? (match[4] = '') : match[4];
const value = match[5];
const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
- const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
+ const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title);
// if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
} else if (value) {
- KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
- (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
+ KeyValueBox.SetField(
+ this.Document,
+ fieldKey,
+ assign + value,
+ Doc.IsDataProto(this.Document) ? true : undefined,
+ assign.includes(':=')
+ ? undefined
+ : (gptval: FieldResult) => {
+ (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string;
+ }
+ );
if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
}
const target = docTitle ? getTitledDoc(docTitle) : undefined;
@@ -361,8 +365,8 @@ export class RichTextRules {
),
// pass the contents between '((' and '))' to chatGPT and append the result
- new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => {
- var count = 0; // ignore first return value which will be the notation that chat is pending a result
+ new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => {
+ let count = 0; // ignore first return value which will be the notation that chat is pending a result
KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
if (count) {
const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
@@ -376,7 +380,7 @@ export class RichTextRules {
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// @(wiki:title)
- new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+ new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => {
const title = match[1].trim().replace(/ /g, '_');
this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
@@ -392,7 +396,7 @@ export class RichTextRules {
// create an inline equation node
// %eq
- new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
+ new InputRule(/%eq/, (state, match, start, end) => {
const fieldKey = 'math' + Utils.GenerateGuid();
this.TextBox.dataDoc[fieldKey] = 'y=';
const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
@@ -400,10 +404,10 @@ export class RichTextRules {
}),
// create an inline view of a tag stored under the '#' field
- new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- //this.Document[DocData]['#' + tag] = '#' + tag;
+ // this.Document[DocData]['#' + tag] = '#' + tag;
const tags = StrListCast(this.Document[DocData].tags);
if (!tags.includes(tag)) {
tags.push(tag);
@@ -417,29 +421,25 @@ export class RichTextRules {
}),
// # heading
- textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
- return { level: match[1].length };
- }),
+ textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
// set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ new InputRule(/[ti!x]$/, (state, match, start, end) => {
if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
const node = (state.doc.resolve(start) as any).nodeAfter;
if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) {
- }
return node
? state.tr
.removeMark(start, end, schema.marks.user_mark)
- .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
- .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
+ .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+ .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
: state.tr;
}),
- new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+ new InputRule(/%\(/, (state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks?.slice() || [];
const mark = state.schema.marks.summarizeInclusive.create();
@@ -452,9 +452,7 @@ export class RichTextRules {
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
}),
- new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())),
],
};
}
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 7ec296ed2..2c366b49b 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
+interface ISummaryView {}
+// currently nothing needs to be rendered for the internal view of a summary.
+// eslint-disable-next-line react/prefer-stateless-function
+export class SummaryViewInternal extends React.Component<ISummaryView> {
+ render() {
+ return null;
+ }
+}
+
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
@@ -32,8 +41,8 @@ export class SummaryView {
};
const js = node.toJSON;
- node.toJSON = function () {
- return js.apply(this, arguments);
+ node.toJSON = function (...args: any[]) {
+ return js.apply(this, args);
};
this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +63,8 @@ export class SummaryView {
const visited = new Set();
for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
- view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ // eslint-disable-next-line no-loop-func
+ view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
if (node.isLeaf && !visited.has(node) && !skip) {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
@@ -87,11 +97,3 @@ export class SummaryView {
this.dom.className = this.className(visible);
};
}
-
-interface ISummaryView {}
-// currently nothing needs to be rendered for the internal view of a summary.
-export class SummaryViewInternal extends React.Component<ISummaryView> {
- render() {
- return <> </>;
- }
-}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ccf7de4a1..6e1f325cf 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,5 @@
-import * as React from 'react';
-import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
-import { Doc } from '../../../../fields/Doc';
+import { DOMOutputSpec, MarkSpec } from 'prosemirror-model';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
@@ -13,7 +12,7 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
id: { default: '' },
},
- toDOM(node: any) {
+ toDOM() {
return ['div', { className: 'dummy' }, 0];
},
},
@@ -45,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -61,7 +60,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM() {
return ['span', { 'data-noAutoLink': 'true' }, 0];
},
},
@@ -128,29 +127,29 @@ export const marks: { [index: string]: MarkSpec } = {
/* FONTS */
pFontFamily: {
- attrs: { family: { default: '' } },
+ attrs: { fontFamily: { default: '' } },
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
const cstyle = getComputedStyle(dom);
if (cstyle.font) {
- if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
- if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
- if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
- if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
- if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
- if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+ if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
+ if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' };
+ if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' };
+ if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' };
+ if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' };
+ if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' };
}
- return { family: '' };
+ return { fontFamily: '' };
},
},
],
- toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontFamily ? ['span', { style: `font-family: "${node.attrs.fontFamily}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: '' } },
+ attrs: { fontColor: { default: '' } },
inclusive: true,
parseDOM: [
{
@@ -160,24 +159,24 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontColor ? ['span', { style: 'color:' + node.attrs.fontColor }] : ['span', 0]),
},
- marker: {
+ pFontHighlight: {
attrs: {
- highlight: { default: 'transparent' },
+ fontHighlight: { default: 'transparent' },
},
inclusive: true,
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
- return { highlight: dom.getAttribute('backgroundColor') };
+ return { fontHighlight: dom.getAttribute('background-color') };
},
},
],
toDOM(node: any) {
- return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+ return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }];
},
},
@@ -336,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = {
const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail() ? ' UM-remote' : '';
return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 62b8b03d6..5bf942218 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,16 +2,17 @@ import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
import { DocServer } from '../../../DocServer';
-import { Doc, Field } from '../../../../fields/Doc';
+import { Doc, Field, FieldType } from '../../../../fields/Doc';
+import { schema } from './schema_rts';
-const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
- hrDOM: DOMOutputSpec = ['hr'],
- preDOM: DOMOutputSpec = ['pre', ['code', 0]],
- brDOM: DOMOutputSpec = ['br'],
- ulDOM: DOMOutputSpec = ['ul', 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
+const hrDOM: DOMOutputSpec = ['hr'];
+const preDOM: DOMOutputSpec = ['pre', ['code', 0]];
+const brDOM: DOMOutputSpec = ['br'];
+// const ulDOM: DOMOutputSpec = ['ul', 0];
-function formatAudioTime(time: number) {
- time = Math.round(time);
+function formatAudioTime(timeIn: number) {
+ const time = Math.round(timeIn);
const hours = Math.floor(time / 60 / 60);
const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
@@ -266,7 +267,7 @@ export const nodes: { [index: string]: NodeSpec } = {
hideValue: { default: false },
editable: { default: true },
},
- leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
+ leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as FieldType),
group: 'inline',
draggable: false,
toDOM(node) {
@@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = {
},
{
style: 'list-style-type=disc',
- getAttrs(dom: any) {
+ getAttrs() {
return { mapStyle: 'bullet' };
},
},
@@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = {
},
],
toDOM(node: Node) {
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index a485ea4c3..b195654ce 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,27 +1,32 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/img-redundant-alt */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/function-component-definition */
import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
import { IconButton } from 'browndash-components';
+import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { CgClose } from 'react-icons/cg';
import { IoMdRedo, IoMdUndo } from 'react-icons/io';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { NumCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
import { Networking } from '../../../Network';
+import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { OpenWhereMod } from '../DocumentView';
-import { ImageBox, ImageEditorData } from '../ImageBox';
+import { ImageEditorData } from '../ImageBox';
import './GenerativeFill.scss';
import Buttons from './GenerativeFillButtons';
import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
-import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import * as React from 'react';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
+import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
enum BrushStyle {
ADD,
@@ -52,7 +57,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// format: array of [image source, corresponding image Doc]
const [edits, setEdits] = useState<(string | Doc)[][]>([]);
const [edited, setEdited] = useState(false);
- const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+ const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
@@ -98,8 +103,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
if (!ctx || !currImg.current || !canvasRef.current) return;
const target = redoStack.current[redoStack.current.length - 1];
- if (!target) {
- } else {
+ if (target) {
undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
const img = new Image();
img.src = target;
@@ -135,7 +139,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
};
// stop brushing, push to undo stack
- const handlePointerUp = (e: React.PointerEvent) => {
+ const handlePointerUp = () => {
const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
if (!ctx) return;
if (!isBrushing) return;
@@ -144,11 +148,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// handles brushing on pointer movement
useEffect(() => {
- if (!isBrushing) return;
+ if (!isBrushing) return undefined;
const canvas = canvasRef.current;
- if (!canvas) return;
+ if (!canvas) return undefined;
const ctx = ImageUtility.getCanvasContext(canvasRef);
- if (!ctx) return;
+ if (!ctx) return undefined;
const handlePointerMove = (e: PointerEvent) => {
const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
@@ -296,6 +300,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
// add the doc to the main freeform
+ // eslint-disable-next-line no-use-before-define
await createNewImgDoc(originalImg.current, true);
}
} else {
@@ -309,12 +314,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
const imgRes = await Promise.all(
imgUrls.map(async url => {
+ // eslint-disable-next-line no-use-before-define
const saveRes = await onSave(url);
return [url, saveRes as Doc];
})
);
setEdits(imgRes);
const image = new Image();
+ // eslint-disable-next-line prefer-destructuring
image.src = imgUrls[0];
ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
currImg.current = image;
@@ -332,7 +339,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const startY = NumCast(parentDoc.current.y);
const children = DocListCast(parentDoc.current.gen_fill_children);
const len = children.length;
- let initialYPositions: number[] = [];
+ const initialYPositions: number[] = [];
for (let i = 0; i < len; i++) {
initialYPositions.push(startY + i * offsetDistanceY);
}
@@ -347,10 +354,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// creates a new image document and returns its reference
const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
- if (!imageRootDoc) return;
- const src = img.src;
+ if (!imageRootDoc) return undefined;
+ const { src } = img;
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
- const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
if (firstDoc) {
const x = 0;
@@ -370,51 +377,51 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}
parentDoc.current = newImg;
return newImg;
- } else {
- if (!parentDoc.current) return;
- const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
- const initialY = 0;
-
- const newImg = Docs.Create.ImageDocument(source, {
- x: x,
- y: initialY,
- _height: freeformRenderSize,
- _width: freeformRenderSize,
- data_nativeWidth: result.nativeWidth,
- data_nativeHeight: result.nativeHeight,
- });
+ }
+ if (!parentDoc.current) return undefined;
+ const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+ const initialY = 0;
+
+ const newImg = Docs.Create.ImageDocument(source, {
+ x: x,
+ y: initialY,
+ _height: freeformRenderSize,
+ _width: freeformRenderSize,
+ data_nativeWidth: result.nativeWidth,
+ data_nativeHeight: result.nativeHeight,
+ });
- const parentList = DocListCast(parentDoc.current.gen_fill_children);
- if (parentList.length > 0) {
- parentList.push(newImg);
- parentDoc.current.gen_fill_children = new List<Doc>(parentList);
- } else {
- parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
- }
+ const parentList = DocListCast(parentDoc.current.gen_fill_children);
+ if (parentList.length > 0) {
+ parentList.push(newImg);
+ parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+ } else {
+ parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+ }
- DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
- adjustImgPositions();
+ DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
+ adjustImgPositions();
- if (isNewCollection && newCollectionRef.current) {
- Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
- } else {
- addDoc?.(newImg);
- }
- return newImg;
+ if (isNewCollection && newCollectionRef.current) {
+ Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+ } else {
+ addDoc?.(newImg);
}
+ return newImg;
};
// Saves an image to the collection
const onSave = async (src: string) => {
const img = new Image();
img.src = src;
- if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+ if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
try {
const res = await createNewImgDoc(img, false);
return res;
} catch (err) {
console.log(err);
}
+ return undefined;
};
// Closes the editor view
@@ -443,12 +450,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}}
/>
}
- label={'Create New Collection'}
+ label="Create New Collection"
labelPlacement="end"
sx={{ whiteSpace: 'nowrap' }}
/>
<Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
- <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} />
+ <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
</div>
</div>
{/* Main canvas for editing */}
@@ -469,7 +476,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
width: cursorData.width,
height: cursorData.width,
}}>
- <div className="innerPointer"></div>
+ <div className="innerPointer" />
</div>
{/* Icons */}
<div className="iconContainer">
@@ -519,11 +526,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
/>
</div>
</div>
- {/* Edits thumbnails*/}
+ {/* Edits thumbnails */}
<div className="editsBox">
{edits.map((edit, i) => (
<img
+ // eslint-disable-next-line react/no-array-index-key
key={i}
+ alt="image edits"
width={75}
src={edit[0] as string}
style={{ cursor: 'pointer' }}
@@ -552,6 +561,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
Original
</label>
<img
+ alt="image stuff"
width={75}
src={originalImg.current?.src}
style={{ cursor: 'pointer' }}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 185ba2280..d1f68ee0e 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,9 +1,9 @@
import './GenerativeFillButtons.scss';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { activeColor } from './generativeFillUtils/generativeFillConstants';
import { Button, IconButton, Type } from 'browndash-components';
import { AiOutlineInfo } from 'react-icons/ai';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
interface ButtonContainerProps {
getEdit: () => Promise<void>;
@@ -11,7 +11,7 @@ interface ButtonContainerProps {
onReset: () => void;
}
-const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
+function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
return (
<div className="generativeFillBtnContainer">
<Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
@@ -20,7 +20,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
text="GET EDITS"
type={Type.TERT}
color={activeColor}
- icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
iconPlacement="right"
onClick={() => {
if (!loading) getEdit();
@@ -36,9 +36,9 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
}}
/>
)}
- <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+ <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
</div>
);
-};
+}
export default Buttons;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
index 97e03ff20..6da8c3da0 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -1,10 +1,6 @@
import { Point } from './generativeFillInterfaces';
export class GenerativeFillMathHelpers {
- static distanceBetween = (p1: Point, p2: Point) => {
- return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
- };
- static angleBetween = (p1: Point, p2: Point) => {
- return Math.atan2(p2.x - p1.x, p2.y - p1.y);
- };
+ static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+ static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y);
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 47a14135f..24dba1778 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -17,15 +17,14 @@ export class ImageUtility {
* @param canvas Canvas to convert
* @returns Blob of canvas
*/
- static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
- return new Promise(resolve => {
+ static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+ new Promise(resolve => {
canvas.toBlob(blob => {
if (blob) {
resolve(blob);
}
}, 'image/png');
});
- };
// given a square api image, get the cropped img
static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
@@ -48,11 +47,12 @@ export class ImageUtility {
}
return canvas;
}
+ return undefined;
};
// converts an image to a canvas data url
- static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
- return new Promise<string>((resolve, reject) => {
+ static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+ new Promise<string>((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = this.getCroppedImg(img, width, height);
@@ -66,7 +66,6 @@ export class ImageUtility {
};
img.src = imageSrc;
});
- };
// calls the openai api to get image edits
static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
@@ -91,7 +90,7 @@ export class ImageUtility {
console.log(data.data);
return {
status: 'success',
- urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+ urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
};
} catch (err) {
console.log(err);
@@ -100,12 +99,10 @@ export class ImageUtility {
};
// mock api call
- static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
- return {
- status: 'success',
- urls: [mockSrc, mockSrc, mockSrc],
- };
- };
+ static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+ status: 'success',
+ urls: [mockSrc, mockSrc, mockSrc],
+ });
// Gets the canvas rendering context of a canvas
static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
@@ -150,12 +147,12 @@ export class ImageUtility {
// Draws the image to the current canvas
static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
- const drawImg = (img: HTMLImageElement) => {
+ const drawImg = (htmlImg: HTMLImageElement) => {
const ctx = this.getCanvasContext(canvasRef);
if (!ctx) return;
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, width, height);
- ctx.drawImage(img, 0, 0, width, height);
+ ctx.drawImage(htmlImg, 0, 0, width, height);
};
if (img.complete) {
@@ -173,7 +170,7 @@ export class ImageUtility {
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext('2d');
- if (!ctx) return;
+ if (!ctx) return undefined;
ctx?.clearRect(0, 0, canvasSize, canvasSize);
ctx.drawImage(paddedCanvas, 0, 0);
@@ -195,7 +192,7 @@ export class ImageUtility {
// Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
+ const { data } = imageData;
for (let i = 0; i < canvas.height; i++) {
for (let j = 0; j < xOffset; j++) {
const targetIdx = 4 * (i * canvas.width + j);
@@ -224,7 +221,7 @@ export class ImageUtility {
// Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
+ const { data } = imageData;
for (let j = 0; j < canvas.width; j++) {
for (let i = 0; i < yOffset; i++) {
const targetIdx = 4 * (i * canvas.width + j);
@@ -256,7 +253,7 @@ export class ImageUtility {
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext('2d');
- if (!ctx) return;
+ if (!ctx) return undefined;
// fix scaling
const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
const width = Math.floor(img.width * scale);
@@ -310,5 +307,6 @@ export class ImageUtility {
} catch (err) {
console.error(err);
}
+ return undefined;
};
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
index 9e620ad11..260923a64 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -1,15 +1,11 @@
-import { Point } from "./generativeFillInterfaces";
+import { Point } from './generativeFillInterfaces';
export class PointerHandler {
- static getPointRelativeToElement = (
- element: HTMLElement,
- e: React.PointerEvent | PointerEvent,
- scale: number
- ): Point => {
- const boundingBox = element.getBoundingClientRect();
- return {
- x: (e.clientX - boundingBox.x) / scale,
- y: (e.clientY - boundingBox.y) / scale,
+ static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => {
+ const boundingBox = element.getBoundingClientRect();
+ return {
+ x: (e.clientX - boundingBox.x) / scale,
+ y: (e.clientY - boundingBox.y) / scale,
+ };
};
- };
}
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 6e7c3e612..317719032 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,7 +1,7 @@
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { DocumentView } from '../DocumentView';
@@ -22,13 +22,14 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div style={{ backgroundColor: 'pink' }}>
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
LayoutTemplateString={undefined}
Document={this.Document}
isContentActive={returnFalse}
addDocument={returnFalse}
ScreenToLocalTransform={this.screenToLocalXf}
- hideResizeHandles={true}
+ hideResizeHandles
/>
</div>
);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 91fdb90fc..0a4efbfe4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
@@ -12,24 +15,23 @@ import { ObjectField } from '../../../../fields/ObjectField';
import { listSpec } from '../../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils';
+import { emptyFunction, emptyPath, stringHash } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
import { SerializationHelper } from '../../../util/SerializationHelper';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
import { CollectionView } from '../../collections/CollectionView';
import { TreeView } from '../../collections/TreeView';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { pinDataTypes as dataTypes, PinProps, ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
@@ -37,32 +39,7 @@ import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-export interface pinDataTypes {
- scrollable?: boolean;
- dataviz?: number[];
- pannable?: boolean;
- type_collection?: boolean;
- inkable?: boolean;
- filters?: boolean;
- pivot?: boolean;
- temporal?: boolean;
- clippable?: boolean;
- datarange?: boolean;
- dataview?: boolean;
- poslayoutview?: boolean;
- dataannos?: boolean;
- map?: boolean;
-}
-export interface PinProps {
- audioRange?: boolean;
- activeFrame?: number;
- currentFrame?: number;
- hidePresBox?: boolean;
- pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
- pinDocLayout?: boolean; // pin layout info (width/height/x/y)
- pinAudioPlay?: boolean; // pin audio annotation
- pinData?: pinDataTypes;
-}
+import { SettingsManager } from '../../../util/SettingsManager';
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -86,6 +63,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
_presTimer: NodeJS.Timeout | undefined;
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: PresBox;
@observable _isChildActive = false;
@@ -126,7 +104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
}
@computed get targetDoc() {
- return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
+ return DocCast(this.activeItem?.presentation_targetDoc);
}
public static targetRenderedDoc = (doc: Doc) => {
const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
@@ -143,6 +121,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get selectedDocumentView() {
if (SelectionManager.Views.length) return SelectionManager.Views[0];
if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
+ return undefined;
}
@computed get isPres() {
return this.selectedDoc === this.Document;
@@ -197,7 +176,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
() => this.layoutDoc.presentation_status === PresStatus.Edit,
editing => editing &&
this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => {
- this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+ this.progressivizedItems(doc)?.forEach(indexedDoc => { indexedDoc.opacity = undefined; });
doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
}) // prettier-ignore
);
@@ -227,13 +206,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
- //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+ // TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
// TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
// No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (slideNum?: number) => {
const nextSlideInd = slideNum ?? this.itemIndex + 1;
let curSlideInd = nextSlideInd;
- //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
+ // CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
this.clearSelectedArray();
const doGroupWithUp =
(nextSelected: number, force = false) =>
@@ -245,7 +224,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (serial) {
this.gotoDocument(nextSelected, this.activeItem, true, async () => {
const waitTime = NumCast(this.activeItem.presentation_duration);
- await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
+ await new Promise<void>(res => {
+ setTimeout(res, Math.max(0, waitTime));
+ });
doGroupWithUp(nextSelected + 1)();
});
} else {
@@ -264,8 +245,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetList = PresBox.targetRenderedDoc(doc);
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);
+ return listItems.filter(ldoc => !ldoc.layout_unrendered);
}
+ return undefined;
};
// go to documents chain
@@ -284,7 +266,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
targetRenderedDoc._dataTransition = 'all 1s';
targetRenderedDoc.opacity = 1;
- setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000);
+ setTimeout(() => {
+ targetRenderedDoc._dataTransition = 'inherit';
+ }, 1000);
const listItems = this.progressivizedItems(this.activeItem);
if (listItems && presIndexed < listItems.length) {
if (!first) {
@@ -305,6 +289,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return true;
}
}
+ return undefined;
};
if (progressiveReveal(false)) return true;
if (this.childDocs[this.itemIndex + 1] !== undefined) {
@@ -314,7 +299,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// before moving onto next slide, run the subroutines :)
const currentDoc = this.childDocs[this.itemIndex];
- //could i do this.childDocs[this.itemIndex] for first arg?
+ // could i do this.childDocs[this.itemIndex] for first arg?
this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);
this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
@@ -334,7 +319,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Called when the user activates 'back' - to move to the previous part of the pres. trail
@action
back = () => {
- const activeItem: Doc = this.activeItem;
+ const { activeItem } = this;
let prevSelected = this.itemIndex;
// Functionality for group with up
let didZoom = activeItem.presentation_movement;
@@ -353,8 +338,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return this.itemIndex;
};
- //The function that is called when a document is clicked or reached through next or back.
- //it'll also execute the necessary actions if presentation is playing.
+ // The function that is called when a document is clicked or reached through next or back.
+ // it'll also execute the necessary actions if presentation is playing.
@undoBatch
public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
Doc.UnBrushAllDocs();
@@ -371,13 +356,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
- this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
+ this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array
this.turnOffEdit();
- this.navigateToActiveItem(finished); //Handles movement to element only when presentationTrail is list
- this.doHideBeforeAfter(); //Handles hide after/before
+ this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list
+ this.doHideBeforeAfter(); // Handles hide after/before
}
});
- static pinDataTypes(target?: Doc): pinDataTypes {
+ static pinDataTypes(target?: Doc): dataTypes {
const targetType = target?.type as any;
const inkable = [DocumentType.INK].includes(targetType);
const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
@@ -388,19 +373,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const datarange = [DocumentType.FUNCPLOT].includes(targetType);
const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
- const type_collection = targetType === DocumentType.COL;
+ const typeCollection = targetType === DocumentType.COL;
const filters = true;
const pivot = true;
const dataannos = false;
- return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
+ return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
}
@action
- playAnnotation = (anno: AudioField) => {};
+ playAnnotation = (/* anno: AudioField */) => {
+ /* empty */
+ };
@action
- static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
+ // eslint-disable-next-line default-param-last
+ static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) {
const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
- if (!bestTarget) return;
+ if (!bestTarget) return undefined;
let changed = false;
if (pinDocLayout) {
if (
@@ -417,20 +405,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
- bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
+ bestTarget[TransitionTimer] = setTimeout(() => {
+ bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined;
+ }, transTime + 10);
changed = true;
}
}
const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
if (activeFrame !== undefined) {
- const transTime = NumCast(activeItem.presentation_transition, 500);
+ const frameTime = 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)?.CollectionFreeFormView;
if (ffview?.childDocs) {
- PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
+ PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime);
ffview.layoutDoc._currentFrame = NumCast(activeFrame);
}
}
@@ -443,12 +433,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
else {
const bestTargetData = bestTarget[DocData];
const current = bestTargetData[fkey];
- const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
+ const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined;
if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
bestTargetData[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);
+ setTimeout(() => {
+ bestTarget._dataTransition = undefined;
+ }, transTime + 10);
}
if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) {
if (bestTarget.xRange !== activeItem.config_xRange) {
@@ -590,7 +582,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
}
});
- setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
+ setTimeout(
+ () =>
+ Array.from(transitioned).forEach(
+ action(doc => {
+ doc._dataTransition = undefined;
+ })
+ ),
+ transTime + 10
+ );
}
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'));
@@ -605,25 +605,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
dv.ComponentView?.brushView?.(viewport, transTime, 2500);
}
- } else {
- 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;
- }
+ } else 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;
}
}
if (changed) {
return bestTargetView?.setViewTransition('all', transTime);
}
+ return undefined;
}
/// copies values from the targetDoc (which is the prototype of the pinDoc) to
/// reserved fields on the pinDoc so that those values can be restored to the
/// target doc when navigating to it.
@action
- static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
+ static pinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
+ const pinDoc = pinDocIn;
pinDoc.presentation = true;
pinDoc.config = '';
if (pinProps.pinDocLayout) {
@@ -652,8 +652,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
}
if (pinProps.pinData.dataannos) {
- const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+ const fieldKey = Doc.LayoutFieldKey(targetDoc);
+ pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fieldKey + '_annotations']).filter(doc => !doc.layout_unrendered));
}
if (pinProps.pinData.inkable) {
pinDoc.config_fillColor = targetDoc.fillColor;
@@ -663,19 +663,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
if (pinProps.pinData.clippable) {
- const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth'];
+ const fieldKey = Doc.LayoutFieldKey(targetDoc);
+ pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth'];
}
if (pinProps.pinData.datarange) {
- pinDoc.config_xRange = undefined; //targetDoc?.xrange;
- pinDoc.config_yRange = undefined; //targetDoc?.yrange;
+ pinDoc.config_xRange = undefined; // targetDoc?.xrange;
+ pinDoc.config_yRange = undefined; // targetDoc?.yrange;
}
if (pinProps.pinData.map) {
// pinDoc.config_latitude = targetDoc?.latitude;
// pinDoc.config_longitude = targetDoc?.longitude;
pinDoc.config_map_zoom = targetDoc?.map_zoom;
pinDoc.config_map_type = targetDoc?.map_type;
- //...
+ // ...
}
if (pinProps.pinData.poslayoutview)
pinDoc.config_pinLayoutData = new List<string>(
@@ -735,8 +735,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* on the right.
*/
navigateToActiveItem = (afterNav?: () => void) => {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
+ const { activeItem, targetDoc } = this;
const finished = () => {
afterNav?.();
targetDoc[Animation] = undefined;
@@ -775,7 +774,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
effect: activeItem,
noSelect: true,
openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
- anchorDoc: activeItem,
easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
playAudio: BoolCast(activeItem.presPlayAudio),
@@ -790,7 +788,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (targetDoc) {
if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;
- DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
+ DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, () => {
// if target or the doc it annotates is not in the lightbox, then close the lightbox
if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
LightboxView.Instance.SetLightboxDoc(undefined);
@@ -822,7 +820,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
opacity = 0;
} else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
opacity = 1;
- setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
+ setTimeout(() => {
+ tagDoc._dataTransition = undefined;
+ }, 1000);
}
}
const hidingIndAft =
@@ -852,12 +852,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const savedStates = docs.map(doc => {
switch (doc.type) {
case DocumentType.COL:
- if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+ if (doc._type_collection === CollectionViewType.Freeform) {
+ return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+ }
break;
case DocumentType.INK:
if (doc.data instanceof InkField) {
return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) };
}
+ break;
+ default:
}
return undefined;
});
@@ -865,7 +869,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._exitTrail = () => {
savedStates
.filter(savedState => savedState)
- .map(savedState => {
+ .forEach(savedState => {
switch (savedState?.type) {
case CollectionViewType.Freeform:
{
@@ -885,6 +889,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doc.color = color;
}
break;
+ default:
}
});
LightboxView.Instance.SetLightboxDoc(undefined);
@@ -903,8 +908,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
- //The function that resets the presentation by removing every action done by it. It also
- //stops the presentaton.
+ // The function that resets the presentation by removing every action done by it. It also
+ // stops the presentaton.
resetPresentation = () => {
this.childDocs
.map(doc => PresBox.targetRenderedDoc(doc))
@@ -921,12 +926,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// The function allows for viewing the pres path on toggle
@action togglePath = (off?: boolean) => {
this._pathBoolean = off ? false : !this._pathBoolean;
- CollectionFreeFormView.ShowPresPaths = this._pathBoolean;
+ SnappingManager.SetShowPresPaths(this._pathBoolean);
};
// The function allows for expanding the view of pres on toggle
@action toggleExpandMode = () => {
- runInAction(() => (this._expandBoolean = !this._expandBoolean));
+ runInAction(() => {
+ this._expandBoolean = !this._expandBoolean;
+ });
this.Document.expandBoolean = this._expandBoolean;
this.childDocs.forEach(doc => {
doc.presentation_expandInlineButton = this._expandBoolean;
@@ -942,7 +949,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const startInd = NumCast(doc.presentation_indexedStart);
this.progressivizedItems(doc)
?.slice(startInd)
- .forEach(indexedDoc => (indexedDoc.opacity = 0));
+ .forEach(indexedDoc => {
+ indexedDoc.opacity = 0;
+ });
doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
}
// if (doc.presentation_hide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
@@ -993,13 +1002,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static minimizedWidth = 198;
public static OpenPresMinimized(doc: Doc, pt: number[]) {
- doc.overlayX = pt[0];
- doc.overlayY = pt[1];
+ [doc.overlayX, doc.overlayY] = pt;
doc._height = 30;
doc._width = PresBox.minimizedWidth;
Doc.AddToMyOverlay(doc);
PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
- return (doc.presentation_status = PresStatus.Manual);
+ doc.presentation_status = PresStatus.Manual;
+ return doc.presentation_status;
}
/**
@@ -1008,12 +1017,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
- //@ts-ignore
- const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
- this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : '');
+ const typeCollection = (e.target as any).selectedOptions[0].value as CollectionViewType;
+ this.layoutDoc.presFieldKey = this.fieldKey + (typeCollection === CollectionViewType.Tree ? '-linearized' : '');
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
- this.Document._type_collection = type_collection;
+ [CollectionViewType.Tree || CollectionViewType.Stacking].includes(typeCollection) && (this.Document._pivotField = undefined);
+ this.Document._type_collection = typeCollection;
if (this.isTreeOrStack) {
this.layoutDoc._gridGap = 0;
}
@@ -1025,10 +1033,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
// @undoBatch
mediaStopChanged = action((e: React.ChangeEvent) => {
- const activeItem: Doc = this.activeItem;
- //@ts-ignore
- const stopDoc = e.target.selectedOptions[0].value as string;
- const stopDocIndex: number = Number(stopDoc[0]);
+ const { activeItem } = this;
+ const stopDoc = (e.target as any).selectedOptions[0].value as string;
+ const stopDocIndex = Number(stopDoc[0]);
activeItem.mediaStopDoc = stopDocIndex;
if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) {
const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
@@ -1049,10 +1056,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return StrCast(activeItem.presentation_movement);
});
- whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isChildActive = isActive));
+ });
// For dragging documents into the presentation trail
addDocumentFilter = (docs: Doc[]) => {
- docs.forEach((doc, i) => {
+ const results = docs.map(doc => {
if (doc.presentation_targetDoc) return true;
if (doc.type === DocumentType.LABEL) {
const audio = Cast(doc.annotationOn, Doc, null);
@@ -1065,13 +1074,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
}
} else if (doc.type !== DocumentType.PRES) {
+ // eslint-disable-next-line operator-assignment
if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
doc.presentation_targetDoc = doc.createdFrom ?? doc; // 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 false;
});
- return true;
+ return !results.some(r => !r);
};
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
@@ -1092,24 +1103,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
if (curDoc && curDoc === this.activeItem)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
<b>
{index + 1}. {curDoc.title}
</b>
</div>
);
- else if (tagDoc)
+ if (tagDoc)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
{index + 1}. {curDoc.title}
</div>
);
- else if (curDoc)
+ if (curDoc)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
{index + 1}. {curDoc.title}
</div>
);
+ return null;
});
}
@@ -1119,15 +1134,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
presDocView && SelectionManager.SelectView(presDocView, false);
};
- focusElement = (doc: Doc, options: FocusViewOptions) => {
+ focusElement = (doc: Doc) => {
this.selectElement(doc);
return undefined;
};
- //Regular click
+ // Regular click
@action
selectElement = (doc: Doc, noNav = false) => {
- CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.());
+ CollectionStackedTimeline.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.());
if (noNav) {
const index = this.childDocs.indexOf(doc);
if (index >= 0 && index < this.childDocs.length) {
@@ -1139,7 +1154,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.updateCurrentPresentation(DocCast(doc.embedContainer));
};
- //Command click
+ // Command click
@action
multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
if (!this.selectedArray.has(doc)) {
@@ -1154,7 +1169,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectPres();
};
- //Shift click
+ // Shift click
@action
shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
this.clearSelectedArray();
@@ -1169,7 +1184,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectPres();
};
- //regular click
+ // regular click
@action
regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => {
this.clearSelectedArray();
@@ -1200,9 +1215,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.layoutDoc.presentation_status === 'edit') {
undoBatch(
action(() => {
- for (const doc of this.selectedArray) {
- this.removeDocument(doc);
- }
+ Array.from(this.selectedArray).forEach(doc => this.removeDocument(doc));
this.clearSelectedArray();
this._eleArray.length = 0;
this._dragArray.length = 0;
@@ -1269,8 +1282,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.childDocs.forEach(doc => this.addToSelectedArray(doc));
handled = true;
}
- default:
break;
+ default:
}
if (handled) {
e.stopPropagation();
@@ -1290,6 +1303,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const tagDoc = PresBox.targetRenderedDoc(doc);
const srcContext = Cast(tagDoc.embedContainer, Doc, null);
const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
+ // eslint-disable-next-line react/no-array-index-key
<div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
<div className="pathOrder-frame">{index + 1}</div>
</div>
@@ -1322,7 +1336,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
order.push(
<>
{labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)}
- <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div>
+ <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }} />
</>
);
}
@@ -1345,17 +1359,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
.filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection)
.forEach((doc, index) => {
const tagDoc = PresBox.targetRenderedDoc(doc);
- 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.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;
- }
+ const [n1x, n1y] = tagDoc //
+ ? [NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2, NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2]
+ : [NumCast(doc.config_panX), NumCast(doc.config_panY)];
+
+ if (index === 0) pathPoints = n1x + ',' + n1y;
+ else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
});
return (
<>
@@ -1401,7 +1410,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
updateTransitionTime = (number: String, change?: number) => {
- PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change);
+ PresBox.SetTransitionTime(
+ number,
+ (timeInMS: number) =>
+ this.selectedArray.forEach(doc => {
+ doc.presentation_transition = timeInMS;
+ }),
+ change
+ );
};
// Converts seconds to ms and updates presentation_transition
@@ -1411,7 +1427,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) scale += change;
if (scale < 0.01) scale = 0.01;
if (scale > 1) scale = 1;
- this.selectedArray.forEach(doc => (doc.config_zoom = scale));
+ this.selectedArray.forEach(doc => {
+ doc.config_zoom = scale;
+ });
};
/*
@@ -1423,76 +1441,96 @@ 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.presentation_duration = timeInMS));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_duration = timeInMS;
+ });
};
@undoBatch
- updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
+ updateMovement = action((movement: PresMovement, all?: boolean) =>
+ (all ? this.childDocs : this.selectedArray).forEach(doc => {
+ doc.presentation_movement = movement;
+ })
+ );
@undoBatch
updateHideBefore = (activeItem: Doc) => {
activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
- this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hideBefore = activeItem.presentation_hideBefore;
+ });
};
@undoBatch
updateHide = (activeItem: Doc) => {
activeItem.presentation_hide = !activeItem.presentation_hide;
- this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hide = activeItem.presentation_hide;
+ });
};
@undoBatch
updateHideAfter = (activeItem: Doc) => {
activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
- this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hideAfter = activeItem.presentation_hideAfter;
+ });
};
@undoBatch
updateOpenDoc = (activeItem: Doc) => {
activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
- this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_openInLightbox = activeItem.presentation_openInLightbox;
+ });
};
@undoBatch
updateEaseFunc = (activeItem: Doc) => {
activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
- this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
+ this.selectedArray.forEach(doc => {
+ doc.presEaseFunc = activeItem.presEaseFunc;
+ });
};
@undoBatch
- updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
+ updateEffectDirection = (effect: PresEffectDirection, all?: boolean) =>
+ (all ? this.childDocs : this.selectedArray).forEach(doc => {
+ doc.presentation_effectDirection = effect;
+ });
@undoBatch
- updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = 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;
static endBatch = () => {
PresBox._sliderBatch.end();
document.removeEventListener('pointerup', PresBox.endBatch, true);
};
- public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
- return (
- <input
- type="range"
- step={step}
- min={min}
- max={max}
- value={value}
- readOnly={true}
- style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
- className={`toolbar-slider ${active ? '' : 'none'}`}
- onPointerDown={e => {
- PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
- document.addEventListener('pointerup', PresBox.endBatch, true);
- e.stopPropagation();
- }}
- onChange={e => {
- e.stopPropagation();
- change(e.target.value);
- }}
- />
- );
- };
+ public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => (
+ <input
+ type="range"
+ step={step}
+ min={min}
+ max={max}
+ value={value}
+ readOnly
+ style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
+ className={`toolbar-slider ${active ? '' : 'none'}`}
+ onPointerDown={e => {
+ PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
+ document.addEventListener('pointerup', PresBox.endBatch, true);
+ e.stopPropagation();
+ }}
+ onChange={e => {
+ e.stopPropagation();
+ change(e.target.value);
+ }}
+ />
+ );
@undoBatch
applyTo = (array: Doc[]) => {
@@ -1500,17 +1538,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
- const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem;
+ // eslint-disable-next-line camelcase
+ const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
array.forEach(curDoc => {
- curDoc.presentation_transition = presentation_transition;
- curDoc.presentation_duration = presentation_duration;
- curDoc.presentation_hideBefore = presentation_hideBefore;
- curDoc.presentation_hideAfter = presentation_hideAfter;
+ curDoc.presentation_transition = pt;
+ curDoc.presentation_duration = pd;
+ curDoc.presentation_hideBefore = ph;
+ curDoc.presentation_hideAfter = pa;
});
};
@computed get visibilityDurationDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const targetType = this.targetDoc.type;
let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0;
@@ -1521,36 +1560,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<Tooltip title={<div className="dash-tooltip">Hide before presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideBefore ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHideBefore(activeItem)}>
Hide before
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Hide while presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHide(activeItem)}>
Hide
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Hide after presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHideAfter(activeItem)}>
Hide after
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Open in lightbox view</div>}>
<div
className="ribbon-toggle"
style={{
- border: `solid 1px ${SettingsManager.userColor}`,
- color: SettingsManager.userColor,
- background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: activeItem.presentation_openInLightbox ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
}}
onClick={() => this.updateOpenDoc(activeItem)}>
Lightbox
@@ -1559,7 +1598,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<Tooltip title={<div className="dash-tooltip">Transition movement style</div>}>
<div
className="ribbon-toggle"
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateEaseFunc(activeItem)}>
{`${StrCast(activeItem.presEaseFunc, 'ease')}`}
</div>
@@ -1569,20 +1608,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<>
<div className="ribbon-doubleButton">
<div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), -1000)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
- <div className={'slider-headers'} style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
+ <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
<div className="slider-text">Short</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Long</div>
@@ -1592,17 +1631,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get progressivizeDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
- const bulletEffect = (effect: PresEffect) => (
+ const bulletEffect = (presEffect: PresEffect) => (
<div
- className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
+ className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
onPointerDown={StopEvent}
- onClick={() => this.updateEffect(effect, true)}>
- {effect}
+ onClick={() => this.updateEffect(presEffect, true)}>
+ {presEffect}
</div>
);
return (
@@ -1611,7 +1651,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Progressivize Collection</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
onChange={() => {
activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
@@ -1622,21 +1662,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// 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 (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations';
if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
}}
- checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
+ checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined}
/>
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Progressivize First Bullet</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
+ onChange={() => {
+ activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1;
+ }}
checked={!NumCast(activeItem.presentation_indexedStart)}
/>
</div>
@@ -1644,9 +1686,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Expand Current Bullet</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
+ onChange={() => {
+ activeItem.presBulletExpand = !activeItem.presBulletExpand;
+ }}
checked={BoolCast(activeItem.presBulletExpand)}
/>
</div>
@@ -1660,20 +1704,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
- border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{effect?.toString()}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
- className={'presBox-dropdownOptions'}
- style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+ className="presBox-dropdownOptions"
+ style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
onPointerDown={e => e.stopPropagation()}>
{Object.values(PresEffect)
.filter(v => isNaN(Number(v)))
- .map(effect => bulletEffect(effect))}
+ .map(peffect => bulletEffect(peffect))}
</div>
</div>
</div>
@@ -1683,7 +1727,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return null;
}
@computed get transitionDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
const preseEffect = (effect: PresEffect) => (
<div
className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
@@ -1698,7 +1742,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
- const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor;
+ const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SnappingManager.userVariantColor : SnappingManager.userColor;
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
@@ -1733,20 +1777,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openMovementDropdown = !this._openMovementDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5,
- border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openMovementDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{this.movementName(activeItem)}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
className="presBox-dropdownOptions"
- id={'presBoxMovementDropdown'}
+ id="presBoxMovementDropdown"
onPointerDown={StopEvent}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
display: this._openMovementDropdown ? 'grid' : 'none',
}}>
{presMovement(PresMovement.None)}
@@ -1758,35 +1802,35 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<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" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{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" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
- <div className={'slider-headers'}>
+ <div className="slider-headers">
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Slow</div>
@@ -1798,9 +1842,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Play Audio Annotation</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
+ onChange={() => {
+ activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio);
+ }}
checked={BoolCast(activeItem.presPlayAudio)}
/>
</div>
@@ -1808,9 +1854,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Zoom Text Selections</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
+ onChange={() => {
+ activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
+ }}
checked={BoolCast(activeItem.presentation_zoomText)}
/>
</div>
@@ -1821,30 +1869,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openEffectDropdown = !this._openEffectDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5,
- border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{effect?.toString()}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
className="presBox-dropdownOptions"
- id={'presBoxMovementDropdown'}
+ id="presBoxMovementDropdown"
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
display: this._openEffectDropdown ? 'grid' : 'none',
}}
onPointerDown={e => e.stopPropagation()}>
{Object.values(PresEffect)
.filter(v => isNaN(Number(v)))
- .map(effect => preseEffect(effect))}
+ .map(presEffect => preseEffect(presEffect))}
</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" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
{StrCast(this.activeItem.presentation_effectDirection)}
</div>
</div>
@@ -1864,33 +1912,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get mediaOptionsDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
const clipStart = NumCast(renderTarget.clipStart);
const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
- const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
+ const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
return (
- <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div>
<div className="ribbon-box">
- Start {'&'} End Time
- <div className={'slider-headers'}>
+ Start & End Time
+ <div className="slider-headers">
<div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+ <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
<input
className="presBox-input"
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
- readOnly={true}
+ readOnly
value={NumCast(activeItem.config_clipStart).toFixed(2)}
onKeyDown={e => e.stopPropagation()}
- onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))}
+ onChange={action(e => {
+ activeItem.config_clipStart = Number(e.target.value);
+ })}
/>
</div>
</div>
@@ -1898,23 +1949,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text" style={{ fontWeight: 500 }}>
Duration (s)
</div>
- <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
- {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
+ <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
+ {Math.round((configClipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
</div>
</div>
<div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+ <div id="endTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
<input
className="presBox-input"
onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
- readOnly={true}
- value={config_clipEnd.toFixed(2)}
- onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))}
+ readOnly
+ value={configClipEnd.toFixed(2)}
+ onChange={action(e => {
+ activeItem.config_clipEnd = Number(e.target.value);
+ })}
/>
</div>
</div>
@@ -1925,15 +1978,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
step="0.1"
min={clipStart}
max={clipEnd}
- value={config_clipEnd}
- style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
+ value={configClipEnd}
+ style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
className={`toolbar-slider ${'end'}`}
id="toolbar-slider"
onPointerDown={e => {
this._batch = UndoManager.StartBatch('config_clipEnd');
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.backgroundColor = SettingsManager.userVariantColor;
+ endBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
}
e.stopPropagation();
}}
@@ -1941,7 +1994,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+ endBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1962,7 +2015,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch = UndoManager.StartBatch('config_clipStart');
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.backgroundColor = SettingsManager.userVariantColor;
+ startBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
}
e.stopPropagation();
}}
@@ -1970,7 +2023,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+ startBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1981,7 +2034,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="slider-headers">
<div className="slider-text">{clipStart.toFixed(2)} s</div>
- <div className="slider-text"></div>
+ <div className="slider-text" />
<div className="slider-text">{clipEnd.toFixed(2)} s</div>
</div>
</div>
@@ -1993,8 +2046,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStart = 'manual')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStart = 'manual';
+ }}
checked={activeItem.presentation_mediaStart === 'manual'}
/>
<div>On click</div>
@@ -2002,9 +2057,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="checkbox-container">
<input
className="presBox-checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_mediaStart = 'auto')}
+ onChange={() => {
+ activeItem.presentation_mediaStart = 'auto';
+ }}
checked={activeItem.presentation_mediaStart === 'auto'}
/>
<div>Automatically</div>
@@ -2016,8 +2073,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStop = 'manual')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStop = 'manual';
+ }}
checked={activeItem.presentation_mediaStop === 'manual'}
/>
<div>At media end time</div>
@@ -2026,8 +2085,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStop = 'auto')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStop = 'auto';
+ }}
checked={activeItem.presentation_mediaStop === 'auto'}
/>
<div>On slide change</div>
@@ -2055,6 +2116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get newDocumentToolbarDropdown() {
return (
@@ -2118,9 +2180,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get newDocumentDropdown() {
return (
- <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div className="ribbon-box">
- Slide Title: <br></br>
+ Slide Title: <br />
<input
className="ribbon-textInput"
placeholder="..."
@@ -2129,16 +2191,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onChange={e => {
e.stopPropagation();
e.preventDefault();
- runInAction(() => (this.title = e.target.value));
- }}></input>
+ runInAction(() => {
+ this.title = e.target.value;
+ });
+ }}
+ />
</div>
<div className="ribbon-box">
Choose type:
<div className="ribbon-doubleButton">
- <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ <div
+ title="Text"
+ className="ribbon-toggle"
+ style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }}
+ onClick={action(() => {
+ this.addFreeform = !this.addFreeform;
+ })}>
Text
</div>
- <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ <div
+ title="Freeform"
+ className="ribbon-toggle"
+ style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }}
+ onClick={action(() => {
+ this.addFreeform = !this.addFreeform;
+ })}>
Freeform
</div>
</div>
@@ -2146,23 +2223,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
Preset layouts:
<div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'blank';
+ })}
+ />
+ <div
+ className="layout"
+ style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'title';
+ })}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'header';
+ })}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
Section header
</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'content';
+ })}>
<div className="title" style={{ alignSelf: 'center' }}>
Title
</div>
<div className="content">Text goes here</div>
</div>
- <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'twoColumns';
+ })}>
<div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
Title
</div>
@@ -2174,8 +2277,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
</div>
- <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
- <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
+ <div
+ className="open-layout"
+ onClick={action(() => {
+ this.openLayouts = !this.openLayouts;
+ })}>
+ <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="caret-down" size="lg" />
</div>
</div>
<div className="ribbon-final-box">
@@ -2190,17 +2297,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
createNewSlide = (layout?: string, title?: string, freeform?: boolean) => {
- let doc = undefined;
+ let doc;
if (layout) doc = this.createTemplate(layout);
if (freeform && layout) doc = this.createTemplate(layout, title);
if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title });
if (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().presentation_targetDoc ?? tab;
+ const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
+ const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab;
const data = Cast(presCollection?.data, listSpec(Doc));
- const config_data = Cast(this.Document.data, listSpec(Doc));
- if (data && config_data) {
+ const configData = Cast(this.Document.data, listSpec(Doc));
+ if (data && configData) {
data.push(doc);
this._props.pinToPres(doc, {});
this.gotoDocument(this.childDocs.length, this.activeItem);
@@ -2222,12 +2329,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' });
// prettier-ignore
switch (layout) {
- case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
- case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+ case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y });
+ case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input || 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'header': return Docs.Create.FreeformDocument([header()], { title: input || 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input || 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input || 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+ default:
}
+ return undefined;
};
// Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2270,17 +2379,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@action
- toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
+ toggleProperties = () => {
+ SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250;
+ };
@computed get toolbar() {
const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
const mode = StrCast(this.Document._type_collection) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
- const activeColor = SettingsManager.userVariantColor;
- const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
+ const activeColor = SnappingManager.userVariantColor;
+ const inactiveColor = lightOrDark(SnappingManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SnappingManager.userBackgroundColor;
return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
- <div id="toolbarContainer" className={'presBox-toolbar'}>
+ <div id="toolbarContainer" className="presBox-toolbar">
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
<FontAwesomeIcon icon={"plus"} />
<FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
@@ -2290,7 +2401,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
className="toolbar-button"
onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
- <FontAwesomeIcon icon={'exchange-alt'} />
+ <FontAwesomeIcon icon="exchange-alt" />
</div>
</Tooltip>
{isMini ? null : (
@@ -2298,12 +2409,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="toolbar-divider" />
<Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
<div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
- <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className="toolbar-thumbtack" icon="keyboard" style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className="toolbar-thumbtack" icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2348,7 +2459,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.gotoDocument(this.itemIndex, this.activeItem);
}
})}>
- <FontAwesomeIcon icon={'play-circle'} />
+ <FontAwesomeIcon icon="play-circle" />
<div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
{mode === CollectionViewType.Carousel3D || isMini ? null : (
@@ -2357,7 +2468,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={action(() => {
if (this.childDocs.length) this._presentTools = !this._presentTools;
})}>
- <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="angle-down" />
{this.presentDropdown}
</div>
)}
@@ -2378,12 +2489,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// 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.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
- <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Loop</div>}>
<div
className="presPanel-button"
style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }}
- onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
- <FontAwesomeIcon icon={'redo-alt'} />
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ () => {
+ this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+ },
+ false,
+ false
+ )
+ }>
+ <FontAwesomeIcon icon="redo-alt" />
</div>
</Tooltip>
<div className="presPanel-divider" />
@@ -2408,7 +2531,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
false
)
}>
- <FontAwesomeIcon icon={'arrow-left'} />
+ <FontAwesomeIcon icon="arrow-left" />
</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)}>
@@ -2436,10 +2559,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
false
)
}>
- <FontAwesomeIcon icon={'arrow-right'} />
+ <FontAwesomeIcon icon="arrow-right" />
</div>
- <div className="presPanel-divider"></div>
- <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+ <div className="presPanel-divider" />
+ <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
<div
className="presPanel-button"
style={{ border: 'solid 1px white' }}
@@ -2463,7 +2586,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
{this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
- <div className="presPanel-divider"></div>
+ <div className="presPanel-divider" />
{this._props.PanelWidth() > 250 ? (
<div
className="presPanel-button-text"
@@ -2477,7 +2600,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
) : (
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}>
- <FontAwesomeIcon icon={'times'} />
+ <FontAwesomeIcon icon="times" />
</div>
)}
</div>
@@ -2492,7 +2615,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@action
- prevClicked = (e: PointerEvent) => {
+ prevClicked = () => {
this.back();
if (this._presTimer) {
clearTimeout(this._presTimer);
@@ -2501,7 +2624,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@action
- nextClicked = (e: PointerEvent) => {
+ nextClicked = () => {
this.next();
if (this._presTimer) {
clearTimeout(this._presTimer);
@@ -2517,7 +2640,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
AddToMap = (treeViewDoc: Doc, index: number[]) => {
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;
+ let indexNum = 0;
for (let i = 0; i < index.length; i++) {
indexNum += index[i] * 10 ** -i;
}
@@ -2529,19 +2652,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
}
}
+ return undefined;
};
SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc);
- RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+ RemFromMap = (treeViewDoc: Doc) => {
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));
}
+ return undefined;
};
- 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]);
+ sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
render() {
// needed to ensure that the childDocs are loaded for looking up fields
@@ -2553,21 +2678,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
(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))}>
+ <div
+ className="miniPres"
+ onClick={e => e.stopPropagation()}
+ onPointerEnter={action(() => {
+ this._forceKeyEvents = true;
+ })}>
<div
className="presPanelOverlay"
style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
- <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Loop</div>}>
<div
className="presPanel-button"
style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
- onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
- <FontAwesomeIcon icon={'redo-alt'} />
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ () => {
+ this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+ },
+ false,
+ false
+ )
+ }>
+ <FontAwesomeIcon icon="redo-alt" />
</div>
</Tooltip>
- <div className="presPanel-divider"></div>
+ <div className="presPanel-divider" />
<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'} />
+ <FontAwesomeIcon icon="arrow-left" />
</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)}>
@@ -2575,10 +2717,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
<div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
- <FontAwesomeIcon icon={'arrow-right'} />
+ <FontAwesomeIcon icon="arrow-right" />
</div>
- <div className="presPanel-divider"></div>
- <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+ <div className="presPanel-divider" />
+ <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
<div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
<b>1</b>
</div>
@@ -2602,27 +2744,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="Slide">
{mode !== CollectionViewType.Invalid ? (
<CollectionView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
PanelWidth={this._props.PanelWidth}
PanelHeight={this.panelHeight}
- childIgnoreNativeSize={true}
+ childIgnoreNativeSize
moveDocument={returnFalse}
- ignoreUnrendered={true}
+ ignoreUnrendered
childDragAction={dropActionType.move}
setContentViewBox={emptyFunction}
- //childLayoutFitWidth={returnTrue}
+ // childLayoutFitWidth={returnTrue}
childOpacity={returnOne}
childClickScript={PresBox.navigateToDocScript}
childLayoutTemplate={this.childLayoutTemplate}
childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
filterAddDocument={this.addDocumentFilter}
removeDocument={returnFalse}
- dontRegisterView={true}
+ dontRegisterView
focus={this.focusElement}
ScreenToLocalTransform={this.getTransform}
AddToMap={this.AddToMap}
RemFromMap={this.RemFromMap}
- hierarchyIndex={emptyPath as any as number[]}
+ hierarchyIndex={emptyPath}
/>
) : null}
</div>
@@ -2641,6 +2784,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
PresBox.NavigateToTarget(bestTarget, activeItem);
});
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 28139eb14..5fa32ad12 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,13 +1,16 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
@@ -19,9 +22,9 @@ import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { StyleProp } from '../../StyleProvider';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
import { PresBox } from './PresBox';
import './PresElementBox.scss';
import { PresMovement } from './PresEnums';
@@ -71,7 +74,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// computes index of this presentation slide in the presBox list
@computed get indexInPres() {
- return this.presBoxView?.SlideIndex(this.slideDoc);
+ return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0;
}
@computed get selectedArray() {
@@ -86,7 +89,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc.layout_hideLinkButton = true;
this._heightDisposer = reaction(
() => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
- ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
+ ({ expand, height }) => {
+ this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0);
+ },
{ fireImmediately: true }
);
}
@@ -94,12 +99,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._heightDisposer?.();
}
- presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
+ presExpandDocumentClick = () => {
+ this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton;
+ };
embedHeight = () => this.collapsedHeight + this.expandViewHeight;
embedWidth = () => this._props.PanelWidth() / 2;
- styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
- return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
- };
+ // prettier-ignore
+ styleProvider = ( doc: Doc | undefined, props: Opt<FieldViewProps>, property: string ): any =>
+ (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property));
/**
* The function that is responsible for rendering a preview or not for this
* presentation element.
@@ -113,7 +120,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
PanelHeight={this.embedHeight}
isContentActive={this._props.isContentActive}
styleProvider={this.styleProvider}
- hideLinkButton={true}
+ hideLinkButton
ScreenToLocalTransform={Transform.Identity}
renderDepth={this._props.renderDepth + 1}
containerViewPath={returnEmptyDoclist}
@@ -150,7 +157,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
ref={this._titleRef}
editing={undefined}
contents={doc.title}
- overflow={'ellipsis'}
+ overflow="ellipsis"
GetValue={() => StrCast(doc.title)}
SetValue={(value: string) => {
doc.title = !value.trim().length ? '-untitled-' : value;
@@ -177,10 +184,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.preventDefault();
if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
- setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => {
- e.stopPropagation();
- e.preventDefault();
- this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => {
+ clickEv.stopPropagation();
+ clickEv.preventDefault();
+ this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey);
this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
});
}
@@ -209,7 +216,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else if (dragArray.length >= 1) {
const doc = document.createElement('div');
doc.className = 'presItem-multiDrag';
- doc.innerText = 'Move ' + this.selectedArray?.size + ' slides';
+ doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides';
doc.style.position = 'absolute';
doc.style.top = e.clientY + 'px';
doc.style.left = e.clientX - 50 + 'px';
@@ -217,7 +224,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (activeItem) {
- runInAction(() => (this._dragging = true));
+ runInAction(() => {
+ this._dragging = true;
+ });
DragManager.StartDocumentDrag(
dragItem.map(ele => ele),
dragData,
@@ -225,7 +234,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.clientY,
undefined,
action(() => {
- Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+ Array.from(classesToRestore).forEach(pair => {
+ // eslint-disable-next-line prefer-destructuring
+ pair[0].className = pair[1];
+ });
this._dragging = false;
})
);
@@ -234,7 +246,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
};
- onPointerOver = (e: any) => {
+ onPointerOver = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointermove', this.onPointerMove);
};
@@ -244,7 +256,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
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.
+ const y = e.clientY - rect.top; // y position within the element.
const height = slide.clientHeight;
const halfLine = height / 2;
if (y <= halfLine) {
@@ -258,7 +270,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
document.removeEventListener('pointermove', this.onPointerMove);
};
- onPointerLeave = (e: any) => {
+ onPointerLeave = () => {
const slide = this._itemRef.current;
if (slide) {
slide.style.borderTop = '0px';
@@ -340,7 +352,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
hideRecording = undoable(
- action((e: React.MouseEvent, iconClick: boolean = false) => {
+ action((e: React.MouseEvent) => {
e.stopPropagation();
this.removeAllRecordingInOverlay();
}),
@@ -395,7 +407,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
lfg = (e: React.MouseEvent) => {
e.stopPropagation();
// TODO: fix this bug
- const { toggleChildrenRun } = this.slideDoc;
+ // const { toggleChildrenRun } = this.slideDoc;
TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
// call this.slideDoc.recurChildren() to get all the children
@@ -406,15 +418,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
get toolbarWidth(): number {
const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
const width = NumCast(this.presBox?._width);
- return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
+ return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
}
@computed get presButtons() {
- const presBox = this.presBox;
+ const { presBox, targetDoc, slideDoc: activeItem } = this;
const presBoxColor = StrCast(presBox?._backgroundColor);
const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
- const targetDoc = this.targetDoc;
- const activeItem = this.slideDoc;
const hasChildren = BoolCast(this.slideDoc?.hasChildren);
const items: JSX.Element[] = [];
@@ -441,7 +451,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
items.push(
<Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
- <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+ <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
<FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
@@ -461,7 +471,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}>
<div
className="slideButton"
- onClick={() => (activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3)}
+ onClick={() => {
+ activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3;
+ }}
style={{
zIndex: 1000 - this.indexInPres,
fontWeight: 700,
@@ -471,7 +483,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
}}>
<div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
- <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon="arrow-up" onPointerDown={e => e.stopPropagation()} />
</div>
</div>
</Tooltip>
@@ -500,15 +512,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.lfg(e);
}}
style={{ fontWeight: 700 }}>
- <FontAwesomeIcon icon={'circle-play'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon="circle-play" onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
}
items.push(
<Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
- <div className={'slideButton'} onClick={this.removePresentationItem}>
- <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
+ <div className="slideButton" onClick={this.removePresentationItem}>
+ <FontAwesomeIcon icon="trash" onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
@@ -516,18 +528,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get mainItem() {
- const isSelected: boolean = this.selectedArray?.has(this.slideDoc) ? true : false;
+ const { presBox, slideDoc: activeItem } = this;
+ const isSelected: boolean = !!this.selectedArray?.has(activeItem);
const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
const miniView: boolean = this.toolbarWidth <= 110;
- const presBox = this.presBox; //presBox
const presBoxColor: string = StrCast(presBox?._backgroundColor);
const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
- const activeItem: Doc = this.slideDoc;
return (
<div
className="presItem-container"
- key={this.slideDoc[Id] + this.indexInPres}
+ key={activeItem[Id] + this.indexInPres}
ref={this._itemRef}
style={{
backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
@@ -537,9 +548,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
}}
- onDoubleClick={action(e => {
+ onDoubleClick={action(() => {
this.toggleProperties();
- this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, false);
+ this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false);
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
@@ -551,11 +562,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
) : (
<div
ref={this._dragRef}
- className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
+ className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`}
style={{
display: 'infline-block',
backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
- //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+ // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
}}>
<div
@@ -563,7 +574,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
display: 'inline-flex',
pointerEvents: isSelected ? undefined : 'none',
- width: `calc(100% ${this.slideDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+ width: `calc(100% ${activeItem.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
cursor: isSelected ? 'text' : 'grab',
}}>
<div
@@ -576,7 +587,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}}
onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}</div>
- <EditableView ref={this._titleRef} oneLine={true} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
+ <EditableView ref={this._titleRef} oneLine editing={!isSelected ? false : undefined} contents={activeItem.title} overflow="ellipsis" GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
</div>
{/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
{/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 8f3f7b03a..7b18974df 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
-export * from "./PresBox";
-export * from "./PresElementBox";
-export * from "./PresEnums"; \ No newline at end of file
+export * from './PresBox';
+export * from './PresElementBox';
+export * from './PresEnums';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 0b3ba81d3..f02c56471 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -4,7 +4,8 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult } from 'react-color';
-import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
@@ -12,10 +13,11 @@ import { SettingsManager } from '../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import './AnchorMenu.scss';
-import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { GPTPopup } from './GPTPopup/GPTPopup';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: AnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -36,7 +38,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
// GPT additions
@observable private selectedText: string = '';
@action
- public setSelectedText = (txt: string) => (this.selectedText = txt);
+ public setSelectedText = (txt: string) => {
+ this.selectedText = txt;
+ };
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -45,8 +49,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string) => Opt<Doc> = (/* color: string */) => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = emptyFunction;
public Delete: () => void = unimplementedFunction;
public PinToPres: () => void = unimplementedFunction;
public MakeTargetToggle: () => void = unimplementedFunction;
@@ -63,7 +67,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
() => SelectionManager.Views.slice(),
- sel => AnchorMenu.Instance.fadeOut(true)
+ () => AnchorMenu.Instance.fadeOut(true)
);
}
@@ -71,7 +75,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* Invokes the API with the selected text and stores it in the summarized text.
* @param e pointer down event
*/
- gptSummarize = async (e: React.PointerEvent) => {
+ gptSummarize = async () => {
GPTPopup.Instance?.setSelectedText(this.selectedText);
GPTPopup.Instance.generateSummary();
};
@@ -80,29 +84,29 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartDrag(e, this._commentRef.current!);
+ (moveEv: PointerEvent) => {
+ this.StartDrag(moveEv, this._commentRef.current!);
return true;
},
returnFalse,
- e => this.OnClick?.(e)
+ clickEv => this.OnClick?.(clickEv)
);
};
audioDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv));
};
cropDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartCropDrag(e, this._cropRef.current!);
+ (moveEv: PointerEvent) => {
+ this.StartCropDrag(moveEv, this._cropRef.current!);
return true;
},
returnFalse,
- e => this.OnCrop?.(e)
+ clickev => this.OnCrop?.(clickev)
);
};
@@ -117,7 +121,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<Group>
<IconButton
icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
- tooltip={'Click to Highlight'}
+ tooltip="Click to Highlight"
onClick={this.highlightClicked}
colorPicker={this.highlightColor}
color={SettingsManager.userColor}
@@ -133,7 +137,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
hsl: { a: 0, h: 0, s: 0, l: 0 },
rgb: { a: 0, r: 0, b: 0, g: 0 },
};
- this.highlightColor = Utils.colorString(col);
+ this.highlightColor = ClientUtils.colorString(col);
};
/**
@@ -156,7 +160,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
</div>
- {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
+ {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
<IconButton
tooltip="Summarize with AI" //
@@ -176,7 +180,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<Popup
tooltip="Find document to link to selected text" //
type={Type.PRIM}
- icon={<FontAwesomeIcon icon={'search'} />}
+ icon={<FontAwesomeIcon icon="search" />}
popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
color={SettingsManager.userColor}
/>
@@ -219,7 +223,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
)}
{this.IsTargetToggler !== returnFalse && (
<Toggle
- tooltip={'Make target visibility toggle on click'}
+ tooltip="Make target visibility toggle on click"
type={Type.PRIM}
toggleType={ToggleType.BUTTON}
toggleStatus={this.IsTargetToggler()}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index a1f5ce703..38578837a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,18 +1,20 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { OpenWhere } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { AnchorMenu } from './AnchorMenu';
import './Annotation.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+import { Highlight } from '../../../fields/DocSymbols';
interface IAnnotationProps extends FieldViewProps {
anno: Doc;
@@ -27,11 +29,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
super(props);
makeObservable(this);
}
+
+ @computed get linkHighlighted() {
+ const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => {
+ const a1 = LinkManager.getOppositeAnchor(link, this._props.anno);
+ return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+ });
+ return found;
+ }
+ linkHighlightedFunc = () => this.linkHighlighted;
+ highlightedFunc = () => this._props.anno[Highlight];
+
+ deleteAnnotation = undoable(() => {
+ const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
+ this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno));
+ AnchorMenu.Instance.fadeOut(true);
+ this._props.select(false);
+ }, 'delete annotation');
+
+ pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres');
+
render() {
return (
<div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
{DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
- <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} />
+ // eslint-disable-next-line no-use-before-define
+ <RegionAnnotation
+ pointerEvents={this._props.pointerEvents}
+ {...this._props}
+ highlighted={this.highlightedFunc}
+ linkHighlighted={this.linkHighlightedFunc}
+ pinToPres={this.pinToPres}
+ deleteAnnotation={this.deleteAnnotation}
+ document={a}
+ key={a[Id]}
+ />
))}
</div>
);
@@ -40,49 +72,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
interface IRegionAnnotationProps extends IAnnotationProps {
document: Doc;
+ linkHighlighted: () => Doc | undefined;
+ highlighted: () => any;
+ deleteAnnotation: () => void;
+ pinToPres: (...args: any[]) => void;
pointerEvents?: () => Opt<string>;
}
@observer
class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- @computed get annoTextRegion() {
- return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document;
+ @computed get regionDoc() {
+ return DocCast(this._props.document.embedContainer, this._props.document);
}
- @undoBatch
- deleteAnnotation = () => {
- const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
- this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
- AnchorMenu.Instance.fadeOut(true);
- this._props.select(false);
- };
-
- @undoBatch
- pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
+ makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
- @undoBatch
- makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
+ isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle);
- isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
- @undoBatch
- showTargetTrail = (anchor: Doc) => {
+ showTargetTrail = undoable((anchor: Doc) => {
const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
this._props.addDocTab(trail, OpenWhere.replaceRight);
}
- };
+ }, 'show target trail');
@action
onContextMenu = (e: React.MouseEvent) => {
AnchorMenu.Instance.Status = 'annotation';
- AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+ AnchorMenu.Instance.Delete = this._props.deleteAnnotation;
AnchorMenu.Instance.Pinned = false;
- AnchorMenu.Instance.PinToPres = this.pinToPres;
- AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+ AnchorMenu.Instance.PinToPres = this._props.pinToPres;
+ AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
- AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+ AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc);
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
e.stopPropagation();
e.preventDefault();
@@ -94,19 +118,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
e.preventDefault();
} else if (e.button === 0) {
e.stopPropagation();
- LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
+ LinkFollower.FollowLink(undefined, this.regionDoc, false);
}
};
- @computed get linkHighlighted() {
- for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) {
- const a1 = LinkManager.getOppositeAnchor(link, this._props.document);
- if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true;
- }
- }
-
render() {
- const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
+ const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc);
return (
<div
className="htmlAnnotation"
@@ -128,8 +145,8 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
height: NumCast(this._props.document._height),
opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
pointerEvents: this._props.pointerEvents?.() as any,
- outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
- backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor),
+ outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined,
+ backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor),
}}
/>
);
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 29b1ca365..560f2fd27 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,12 +1,13 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
+import { Button, IconButton, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CgClose } from 'react-icons/cg';
import ReactLoading from 'react-loading';
import { TypeAnimation } from 'react-type-animation';
-import { Utils } from '../../../../Utils';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { Networking } from '../../../Network';
@@ -27,6 +28,7 @@ interface GPTPopupProps {}
@observer
export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GPTPopup;
@observable private chatMode: boolean = false;
@@ -59,7 +61,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
public dataChatPrompt: string | null = null;
@action
public setDataJson = (text: string) => {
- if (text=="") this.dataChatPrompt = "";
+ if (text == '') this.dataChatPrompt = '';
this.dataJson = text;
};
@@ -87,8 +89,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
@observable
public highlightRange: number[] = [];
@action callSummaryApi = () => {};
- @action callEditApi = () => {};
- @action replaceText = (replacement: string) => {};
@observable
private done: boolean = false;
@@ -127,19 +127,19 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
* Generates a Dalle image and uploads it to the server.
*/
generateImage = async () => {
- if (this.imgDesc === '') return;
+ if (this.imgDesc === '') return undefined;
this.setImgUrls([]);
this.setMode(GPTPopupMode.IMAGE);
this.setVisible(true);
this.setLoading(true);
try {
- let image_urls = await gptImageCall(this.imgDesc);
+ const image_urls = await gptImageCall(this.imgDesc);
console.log('Image urls: ', image_urls);
if (image_urls && image_urls[0]) {
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
console.log('Upload result: ', result);
- const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
console.log('Upload source: ', source);
this.setImgUrls([[image_urls[0], source]]);
}
@@ -147,6 +147,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
console.error(err);
}
this.setLoading(false);
+ return undefined;
};
generateSummary = async () => {
@@ -161,7 +162,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
console.error(err);
}
GPTPopup.Instance.setLoading(false);
- }
+ };
generateDataAnalysis = async () => {
GPTPopup.Instance.setVisible(true);
@@ -173,7 +174,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
console.error(err);
}
GPTPopup.Instance.setLoading(false);
- }
+ };
/**
* Transfers the summarization text to a sidebar annotation text document.
@@ -224,7 +225,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
*/
private chatWithAI = () => {
this.chatMode = true;
- }
+ };
dataPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
this.dataChatPrompt = e.target.value;
});
@@ -243,30 +244,24 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}
};
- imageBox = () => {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
- {this.heading('GENERATED IMAGE')}
- <div className="image-content-wrapper">
- {this.imgUrls.map(rawSrc => (
- <div className="img-wrapper">
- <div className="img-container">
- <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
- </div>
- <div className="btn-container">
- <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
- </div>
+ imageBox = () => (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('GENERATED IMAGE')}
+ <div className="image-content-wrapper">
+ {this.imgUrls.map(rawSrc => (
+ <div className="img-wrapper">
+ <div className="img-container">
+ <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
</div>
- ))}
- </div>
- {!this.loading && (
- <>
- <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
- </>
- )}
+ <div className="btn-container">
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ </div>
+ </div>
+ ))}
</div>
- );
- };
+ {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />}
+ </div>
+ );
summaryBox = () => (
<>
@@ -320,7 +315,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
dataAnalysisBox = () => (
<>
<div>
- {this.heading("ANALYSIS")}
+ {this.heading('ANALYSIS')}
<div className="content-wrapper">
{!this.loading &&
(!this.done ? (
@@ -342,8 +337,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
</div>
{!this.loading && (
<div className="btns-wrapper">
- {this.done?
- this.chatMode?(
+ {this.done ? (
+ this.chatMode ? (
<input
defaultValue=""
autoComplete="off"
@@ -356,19 +351,26 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
placeholder="Ask GPT a question about the data..."
id="search-input"
className="searchBox-input"
- style={{width: "100%"}}
+ style={{ width: '100%' }}
/>
- )
- :(
- <>
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
- <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
- </>
+ ) : (
+ <>
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ </>
+ )
) : (
<div className="summarizing">
<span>Summarizing</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT}/>
+ <Button
+ text="Stop Animation"
+ onClick={() => {
+ this.setDone(true);
+ }}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ type={Type.TERT}
+ />
</div>
)}
</div>
@@ -382,9 +384,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
<FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} />
AI generated responses can contain inaccurate or misleading content.
</div>
- ) : (
- <></>
- );
+ ) : null;
heading = (headingText: string) => (
<div className="summary-heading">
@@ -396,7 +396,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
render() {
return (
<div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
- {this.mode === GPTPopupMode.SUMMARY? this.summaryBox() : this.mode === GPTPopupMode.DATA? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
+ {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
</div>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index aaff2a342..a3fd192f7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
@@ -10,7 +12,8 @@ import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
import { DocUtils } from '../../documents/Documents';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -26,9 +29,8 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
-const _global = (window /* browser */ || global) /* node */ as any;
-//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
@@ -42,6 +44,7 @@ interface IViewerProps extends FieldViewProps {
url: string;
sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
loaded?: (nw: number, nh: number, np: number) => void;
+ // eslint-disable-next-line no-use-before-define
setPdfViewer: (view: PDFViewer) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
@@ -98,14 +101,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
componentDidMount() {
- runInAction(() => (this._showWaiting = true));
+ runInAction(() => {
+ this._showWaiting = true;
+ });
this.setupPdfJsViewer();
- this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
+ this._mainCont.current?.addEventListener('scroll', e => {
+ (e.target as any).scrollLeft = 0;
+ });
this._disposers.layout_autoHeight = reaction(
() => this._props.layoutDoc._layout_autoHeight,
- layout_autoHeight => {
- if (layout_autoHeight) {
+ layoutAutoHeight => {
+ if (layoutAutoHeight) {
this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
@@ -114,7 +121,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
this._disposers.selected = reaction(
() => this._props.isSelected(),
- selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
+ () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
@@ -164,7 +171,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
)
);
}
- runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
+ runInAction(() => {
+ this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
+ });
};
_scrollStopper: undefined | (() => void);
@@ -176,7 +185,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
let focusSpeed: Opt<number>;
if (doc !== this._props.Document && mainCont) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+ const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -202,14 +211,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+ runInAction(() => {
+ this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1;
+ });
this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
}
document.removeEventListener('pagesinit', this.pagesinit);
- var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
+ let quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
this._disposers.scale = reaction(
() => NumCast(this._props.layoutDoc._freeform_scale, 1),
- scale => (this._pdfViewer.currentScaleValue = scale),
+ scale => {
+ this._pdfViewer.currentScaleValue = scale;
+ },
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
@@ -226,7 +239,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
setTimeout(
() => {
this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
- setTimeout(() => (this._forcedScroll = false), duration);
+ setTimeout(() => {
+ this._forcedScroll = false;
+ }, duration);
},
this._mainCont.current ? 0 : 250
); // wait for mainCont and try again to scroll
@@ -262,7 +277,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
eventBus._on('pagesinit', this.pagesinit);
eventBus._on(
'pagerendered',
- action(() => (this._showWaiting = false))
+ action(() => {
+ this._showWaiting = false;
+ })
);
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
@@ -305,7 +322,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
@observable private _scrollTimer: any = undefined;
- onScroll = (e: React.UIEvent<HTMLElement>) => {
+ onScroll = () => {
if (this._mainCont.current && !this._forcedScroll) {
this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
if (!LinkInfo.Instance?.LinkInfo) {
@@ -340,7 +357,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
query: searchString,
};
if (clear) {
- this._pdfViewer?.eventBus.dispatch('reset', {});
+ this._pdfViewer?.eventBus.dispatch('findbarclose', {});
} else if (!searchString) {
bwd ? this.prevAnnotation() : this.nextAnnotation();
} else if (this._pdfViewer?.pageViewsReady) {
@@ -387,7 +404,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
};
@action
- finishMarquee = (x?: number, y?: number) => {
+ finishMarquee = (/* x?: number, y?: number */) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
this._marqueeref.current?.onTerminateSelection();
@@ -456,13 +473,15 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
- if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -485,6 +504,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
return (
<div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
{inlineAnnos.map(anno => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
<Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
@@ -496,8 +516,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
@@ -517,6 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
}}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -524,7 +545,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
renderDepth={this._props.renderDepth + 1}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
fieldKey={this._props.fieldKey + '_annotations'}
getScrollHeight={this.getScrollHeight}
setPreviewCursor={this.setPreviewCursor}
@@ -532,7 +553,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
PanelWidth={this.panelWidth}
ScreenToLocalTransform={this.overlayTransform}
isAnyChildContentActive={returnFalse}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlayScrollable
childFilters={childFilters}
select={emptyFunction}
styleProvider={this.childStyleProvider}
@@ -577,7 +598,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{this.pdfViewerDiv}
{this.annotationLayer}
{this.overlayLayer}
- {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+ {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null}
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
@@ -585,7 +606,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
getPageFromScroll={this.getPageFromScroll}
anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
- isNativeScaled={true}
+ isNativeScaled
annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
docView={this._props.pdfBox.DocumentView!}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9f153e86d..384e6d654 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,10 +1,11 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
+import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
import { DirectLinks, DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
import { DocCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocUtils } from '../../documents/Documents';
@@ -14,16 +15,111 @@ import { SearchUtil } from '../../util/SearchUtil';
import { SettingsManager } from '../../util/SettingsManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { IRecommendation, Recommendation } from '../newlightbox/components';
import { fetchRecommendations } from '../newlightbox/utils';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './SearchBox.scss';
+import { Id } from '../../../fields/FieldSymbols';
+import { ClientUtils } from '../../../ClientUtils';
const DAMPENING_FACTOR = 0.9;
const MAX_ITERATIONS = 25;
const ERROR = 0.03;
+export interface SearchBoxItemProps {
+ Document: Doc;
+ searchString: string;
+ isLinkSearch: boolean;
+ matchedKeys: string[];
+ className: string;
+ linkFrom: Doc | undefined;
+ selectItem: (doc: Doc) => void;
+ linkCreateAnchor?: () => Doc | undefined;
+ linkCreated?: (link: Doc) => void;
+}
+@observer
+export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> {
+ constructor(props: SearchBoxItemProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ /**
+ * @param {Doc} doc - doc to be selected
+ *
+ * This method selects a doc by either jumping to it (centering/zooming in on it)
+ * or opening it in a new tab.
+ */
+ selectElement = async (doc: Doc, finishFunc: () => void) => {
+ await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
+ };
+
+ /**
+ * @param {Doc} doc - doc of the search result that has been clicked on
+ *
+ * This method is called when the user clicks on a search result. The _selectedResult is
+ * updated accordingly and the doc is highlighted with the selectElement method.
+ */
+ onResultClick = action(async (doc: Doc) => {
+ this._props.selectItem(doc);
+ this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
+ });
+
+ componentWillUnmount(): void {
+ const doc = this._props.Document;
+ DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+ }
+
+ @undoBatch
+ makeLink = action((linkTo: Doc) => {
+ const linkFrom = this._props.linkCreateAnchor?.();
+ if (linkFrom) {
+ const link = DocUtils.MakeLink(linkFrom, linkTo, {});
+ link && this._props.linkCreated?.(link);
+ }
+ });
+
+ render() {
+ // eslint-disable-next-line no-use-before-define
+ const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
+ const { title } = this._props.Document;
+
+ return (
+ <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
+ <div
+ onClick={
+ this._props.isLinkSearch
+ ? () => this.makeLink(this._props.Document)
+ : e => {
+ this.onResultClick(this._props.Document);
+ e.stopPropagation();
+ }
+ }
+ style={{
+ fontWeight: LinkManager.Links(this._props.linkFrom).find(
+ link =>
+ Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) ||
+ Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+ )
+ ? 'bold'
+ : '',
+ }}
+ className={this._props.className}>
+ <div className="searchBox-result-title">{title as string}</div>
+ <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
+ {formattedType}
+ </div>
+ <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
+ {this._props.matchedKeys.join(', ')}
+ </div>
+ </div>
+ </Tooltip>
+ );
+ }
+}
+
export interface SearchBoxProps extends FieldViewProps {
linkSearch: boolean;
linkFrom?: (() => Doc | undefined) | undefined;
@@ -40,6 +136,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(SearchBox, fieldKey);
}
+ // eslint-disable-next-line no-use-before-define
public static Instance: SearchBox;
private _inputRef = React.createRef<HTMLInputElement>();
@@ -109,46 +206,29 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
});
/**
- * @param {Doc} doc - doc of the search result that has been clicked on
- *
- * This method is called when the user clicks on a search result. The _selectedResult is
- * updated accordingly and the doc is highlighted with the selectElement method.
- */
- onResultClick = action(async (doc: Doc) => {
- this._selectedResult = doc;
- this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
- });
-
- @undoBatch
- makeLink = action((linkTo: Doc) => {
- const linkFrom = this._props.linkCreateAnchor?.();
- if (linkFrom) {
- const link = DocUtils.MakeLink(linkFrom, linkTo, {});
- link && this._props.linkCreated?.(link);
- }
- });
-
- /**
* @param {Doc[]} docs - docs to be searched through recursively
* @param {number, Doc => void} func - function to be called on each doc
*
* This method iterates asynchronously through an array of docs and all docs within those
* docs, calling the function func on each doc.
*/
- static async foreachRecursiveDocAsync(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+ static async foreachRecursiveDocAsync(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+ let docs = docsIn;
let newarray: Doc[] = [];
- var depth = 0;
+ let depth = 0;
while (docs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-await-in-loop
await Promise.all(
docs
.filter(d => d)
+ // eslint-disable-next-line no-loop-func
.map(async d => {
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
- const docs = await DocListCastAsync(data);
- docs && newarray.push(...docs);
+ const dataDocs = await DocListCastAsync(data);
+ dataDocs && newarray.push(...dataDocs);
func(depth, d);
})
);
@@ -170,6 +250,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
case DocumentType.IMG : return 'Img';
case DocumentType.RTF : return 'Rtf';
case DocumentType.COL : return 'Col:'+colType.substring(0,3);
+ default:
} // prettier-ignore
return type.charAt(0).toUpperCase() + type.substring(1, 3);
@@ -210,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
if (doc[DocData][DirectLinks].size === 0) {
this._linkedDocsOut.set(doc, new Set(this._results.keys()));
- this._results.forEach((_, linkedDoc) => {
+ this._results.forEach((__, linkedDoc) => {
this._linkedDocsIn.get(linkedDoc)?.add(doc);
});
} else {
@@ -244,7 +325,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
pageRankIteration(): boolean {
let converged = true;
const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
-
const nextPageRanks = new Map<Doc, number>();
this._results.forEach((_, doc) => {
@@ -298,35 +378,27 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
if (query) {
this.searchCollection(query);
const response = await fetchRecommendations('', query, [], true);
- const recs = response.recommendations;
+ const recs = response.recommendations as any[];
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;
+ recs.forEach(rec => {
+ const { title, url, type, text, transcript, previewUrl, embedding, distance, source, related_concepts: relatedConcepts, doc_id: docId } = rec;
recommendations.push({
- title: title,
+ title,
data: url,
- type: type,
- text: text,
- transcript: transcript,
- previewUrl: previewUrl,
- embedding: embedding,
+ type,
+ text,
+ transcript,
+ previewUrl,
+ embedding,
distance: Math.round(distance * 100) / 100,
source: source,
- related_concepts: related_concepts,
- docId: docId,
+ related_concepts: relatedConcepts,
+ docId,
});
- }
- const setRecommendations = action(() => (this._recommendations = recommendations));
+ });
+ const setRecommendations = action(() => {
+ this._recommendations = recommendations;
+ });
setRecommendations();
}
};
@@ -347,16 +419,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
});
/**
- * @param {Doc} doc - doc to be selected
- *
- * This method selects a doc by either jumping to it (centering/zooming in on it)
- * or opening it in a new tab.
- */
- selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
- };
-
- /**
* This method returns a JSX list of the options in the select drop-down menu, which
* is used to filter the types of documents that appear in the search results.
*/
@@ -366,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
return selectValues.map(value => (
<option key={value} value={value}>
- {SearchBox.formatType(value, '')}
+ {ClientUtils.cleanDocumentTypeExt(value as DocumentType)}
</option>
));
}
@@ -375,60 +437,41 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* This method renders the search input box, select drop-down menu, and search results.
*/
render() {
- var validResults = 0;
-
const isLinkSearch: boolean = this._props.linkSearch;
-
const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
+ const resultsJSX = [] as any[];
+ const linkFrom = this._props.linkFrom?.();
- const resultsJSX = Array();
+ let validResults = 0;
+ sortedResults.forEach(([Document, matchedKeys]) => {
+ let className = 'searchBox-results-scroll-view-result';
- const fromDoc = this._props.linkFrom?.();
-
- sortedResults.forEach(result => {
- var className = 'searchBox-results-scroll-view-result';
-
- if (this._selectedResult === result[0]) {
+ if (this._selectedResult === Document) {
className += ' searchBox-results-scroll-view-result-selected';
}
- const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
- const title = result[0].title;
-
- if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
+ if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) {
validResults++;
resultsJSX.push(
- <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}>
- <div
- onClick={
- isLinkSearch
- ? () => this.makeLink(result[0])
- : e => {
- this.onResultClick(result[0]);
- e.stopPropagation();
- }
- }
- style={{
- fontWeight: LinkManager.Links(fromDoc).find(
- link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc)
- )
- ? 'bold'
- : '',
- }}
- className={className}>
- <div className="searchBox-result-title">{title as string}</div>
- <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
- {formattedType}
- </div>
- <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
- {result[1].join(', ')}
- </div>
- </div>
- </Tooltip>
+ <SearchBoxItem
+ key={Document[Id]}
+ Document={Document}
+ selectItem={action((doc: Doc) => {
+ this._selectedResult = doc;
+ })}
+ isLinkSearch={isLinkSearch}
+ searchString={this._searchString}
+ matchedKeys={matchedKeys}
+ linkFrom={linkFrom}
+ className={className}
+ linkCreateAnchor={this._props.linkCreateAnchor}
+ linkCreated={this._props.linkCreated}
+ />
);
}
});
+ // eslint-disable-next-line react/jsx-props-no-spreading
const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />);
return (
@@ -459,7 +502,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
<div className="searchBox-results-container">
<div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
<div className="section-title">Results</div>
- <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
</div>
<div className="searchBox-results-view">{resultsJSX}</div>
</div>
@@ -468,7 +511,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
<div className="searchBox-recommendations-container">
<div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
<div className="section-title">Recommendations</div>
- <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
</div>
<div className="searchBox-recommendations-view">{recommendationsJSX}</div>
</div>
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index c9c01189e..7ad7b2927 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -35,7 +35,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
val: StrCast(doc._id),
color: SettingsManager.userColor,
background: SettingsManager.userBackgroundColor,
- icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />,
+ icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />,
onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
};
})}
diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts
index 1f1db91f6..968e9c2d4 100644
--- a/src/client/views/selectedDoc/index.ts
+++ b/src/client/views/selectedDoc/index.ts
@@ -1 +1 @@
-export * from './SelectedDocView' \ No newline at end of file
+export * from './SelectedDocView';
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index eab33114e..1ab0932a3 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -5,14 +5,14 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Flip } from 'react-awesome-reveal';
import { FaBug } from 'react-icons/fa';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
import { PingManager } from '../../util/PingManager';
import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
@@ -28,6 +28,7 @@ import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentV
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider } from '../StyleProvider';
import './TopBar.scss';
+import { dropActionType } from '../../util/DropActionTypes';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
@@ -35,6 +36,7 @@ import './TopBar.scss';
*/
@observer
export class TopBar extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: TopBar;
@observable private _flipDocumentation = 0;
constructor(props: any) {
@@ -44,10 +46,12 @@ export class TopBar extends ObservableReactComponent<{}> {
}
navigateToHome = () => {
- (CollectionDockingView.Instance?.CaptureThumbnail() ?? new Promise<void>(res => res())).then(() => {
+ (CollectionDockingView.Instance?.CaptureThumbnail() ??
+ new Promise<void>(res => { res(); })) .then(() =>
+ {
Doc.ActivePage = 'home';
DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
- });
+ }); // prettier-ignore
};
@computed get color() {
@@ -61,7 +65,9 @@ export class TopBar extends ObservableReactComponent<{}> {
}
@observable happyHeart: boolean = PingManager.Instance.IsBeating;
- setHappyHeart = action((status: boolean) => (this.happyHeart = status));
+ setHappyHeart = action((status: boolean) => {
+ this.happyHeart = status;
+ });
dispose = reaction(
() => PingManager.Instance.IsBeating,
isBeating => this.setHappyHeart(isBeating)
@@ -85,7 +91,7 @@ export class TopBar extends ObservableReactComponent<{}> {
/>
) : (
<div className="logo-container">
- <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
+ <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo" />
<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>
@@ -192,11 +198,11 @@ export class TopBar extends ObservableReactComponent<{}> {
}}
/>
) : null}
- <IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
+ <IconButton tooltip="Issue Reporter ⌘I" size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<Flip key={this._flipDocumentation}>
- <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="Documentation ⌘D" size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
</Flip>
- <IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
+ <IconButton tooltip="Settings ⌘⇧S" size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.openMgr} icon={<FontAwesomeIcon icon="cog" />} />
<IconButton
size={Size.SMALL}
onClick={ServerStats.Instance.open}
@@ -213,11 +219,13 @@ export class TopBar extends ObservableReactComponent<{}> {
/**
* Make the documentation icon flip around to draw attention to it.
*/
- FlipDocumentationIcon = action(() => (this._flipDocumentation = this._flipDocumentation + 1));
+ FlipDocumentationIcon = action(() => {
+ this._flipDocumentation += 1;
+ });
render() {
return (
- //TODO:glr Add support for light / dark mode
+ // TODO:glr Add support for light / dark mode
<div
style={{
pointerEvents: 'all',
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
deleted file mode 100644
index 5744ebbcd..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ /dev/null
@@ -1,82 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.webcam-cont {
- background: whitesmoke;
- color: grey;
- border-radius: 15px;
- box-shadow: #9c9396 0.2vw 0.2vw 0.4vw;
- border: solid #bbbbbbbb 5px;
- pointer-events: all;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-
- .webcam-header {
- height: 50px;
- text-align: center;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 16px;
- width: 100%;
- margin-top: 20px;
- }
-
- .videoContainer {
- position: relative;
- width: calc(100% - 20px);
- height: 100%;
- /* border: 10px solid red; */
- margin-left: 10px;
- }
-
- .buttonContainer {
- display: flex;
- width: calc(100% - 20px);
- height: 50px;
- justify-content: center;
- text-align: center;
- /* border: 1px solid black; */
- margin-left: 10px;
- margin-top: 0;
- margin-bottom: 15px;
- }
-
- #roomName {
- outline: none;
- border-radius: inherit;
- border: 1px solid #bbbbbbbb;
- margin: 10px;
- padding: 10px;
- }
-
- .side {
- width: 25%;
- height: 20%;
- position: absolute;
- /* top: 65%; */
- z-index: 2;
- right: 0px;
- bottom: 18px;
- }
-
- .main {
- position: absolute;
- width: 100%;
- height: 100%;
- /* top: 20%; */
- align-self: center;
- }
-
- .videoButtons {
- border-radius: 50%;
- height: 30px;
- width: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
- justify-self: center;
- align-self: center;
- margin: 5px;
- border: 1px solid black;
- }
-}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
deleted file mode 100644
index 4e984f3d6..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { IconLookup } from '@fortawesome/fontawesome-svg-core';
-import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { SnappingManager } from '../../util/SnappingManager';
-import '../../views/nodes/WebBox.scss';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import './DashWebRTCVideo.scss';
-import { hangup, initialize, refreshVideos } from './WebCamLogic';
-
-/**
- * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
- */
-@observer
-export class DashWebRTCVideo extends React.Component<FieldViewProps> {
- private roomText: HTMLInputElement | undefined;
- @observable remoteVideoAdded: boolean = false;
-
- @action
- changeUILook = () => (this.remoteVideoAdded = true);
-
- /**
- * Function that submits the title entered by user on enter press.
- */
- private onEnterKeyDown = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- const submittedTitle = this.roomText!.value;
- this.roomText!.value = '';
- this.roomText!.blur();
- initialize(submittedTitle, this.changeUILook);
- }
- };
-
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
- }
-
- onClickRefresh = () => refreshVideos();
-
- onClickHangUp = () => hangup();
-
- render() {
- const content = (
- <div className="webcam-cont" style={{ width: '100%', height: '100%' }}>
- <div className="webcam-header">DashWebRTC</div>
- <input id="roomName" type="text" placeholder="Enter room name" ref={e => (this.roomText = e!)} onKeyDown={this.onEnterKeyDown} />
- <div className="videoContainer">
- <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video>
- <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video>
- </div>
- <div className="buttonContainer">
- <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}>
- <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" />
- </div>
- <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}>
- <FontAwesomeIcon icon={faSync as IconLookup} color="white" />
- </div>
- </div>
- </div>
- );
-
- const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
-
- return (
- <>
- <div className={classname}>{content}</div>
- {!frozen ? null : <div className="webBox-overlay" />}
- </>
- );
- }
-}
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
deleted file mode 100644
index 5f6202bc8..000000000
--- a/src/client/views/webcam/WebCamLogic.js
+++ /dev/null
@@ -1,292 +0,0 @@
-'use strict';
-import io from "socket.io-client";
-
-var socket;
-var isChannelReady = false;
-var isInitiator = false;
-var isStarted = false;
-var localStream;
-var pc;
-var remoteStream;
-var turnReady;
-var room;
-
-export function initialize(roomName, handlerUI) {
-
- var pcConfig = {
- 'iceServers': [{
- 'urls': 'stun:stun.l.google.com:19302'
- }]
- };
-
- // Set up audio and video regardless of what devices are present.
- var sdpConstraints = {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- };
-
- /////////////////////////////////////////////
-
- room = roomName;
-
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`);
-
- if (room !== '') {
- socket.emit('create or join', room);
- console.log('Attempted to create or join room', room);
- }
-
- socket.on('created', function (room) {
- console.log('Created room ' + room);
- isInitiator = true;
- });
-
- socket.on('full', function (room) {
- console.log('Room ' + room + ' is full');
- });
-
- socket.on('join', function (room) {
- console.log('Another peer made a request to join room ' + room);
- console.log('This peer is the initiator of room ' + room + '!');
- isChannelReady = true;
- });
-
- socket.on('joined', function (room) {
- console.log('joined: ' + room);
- isChannelReady = true;
- });
-
- socket.on('log', function (array) {
- console.log.apply(console, array);
- });
-
- ////////////////////////////////////////////////
-
-
- // This client receives a message
- socket.on('message', function (message) {
- console.log('Client received message:', message);
- if (message === 'got user media') {
- maybeStart();
- } else if (message.type === 'offer') {
- if (!isInitiator && !isStarted) {
- maybeStart();
- }
- pc.setRemoteDescription(new RTCSessionDescription(message));
- doAnswer();
- } else if (message.type === 'answer' && isStarted) {
- pc.setRemoteDescription(new RTCSessionDescription(message));
- } else if (message.type === 'candidate' && isStarted) {
- var candidate = new RTCIceCandidate({
- sdpMLineIndex: message.label,
- candidate: message.candidate
- });
- pc.addIceCandidate(candidate);
- } else if (message === 'bye' && isStarted) {
- handleRemoteHangup();
- }
- });
-
- ////////////////////////////////////////////////////
-
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
-
- const gotStream = (stream) => {
- console.log('Adding local stream.');
- localStream = stream;
- localVideo.srcObject = stream;
- sendMessage('got user media');
- if (isInitiator) {
- maybeStart();
- }
- }
-
-
- navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
- .then(gotStream)
- .catch(function (e) {
- alert('getUserMedia() error: ' + e.name);
- });
-
-
-
- var constraints = {
- video: true
- };
-
- console.log('Getting user media with constraints', constraints);
-
- const requestTurn = (turnURL) => {
- var turnExists = false;
- for (var i in pcConfig.iceServers) {
- if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') {
- turnExists = true;
- turnReady = true;
- break;
- }
- }
- if (!turnExists) {
- console.log('Getting TURN server from ', turnURL);
- // No TURN server. Get one from computeengineondemand.appspot.com:
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4 && xhr.status === 200) {
- var turnServer = JSON.parse(xhr.responseText);
- console.log('Got TURN server: ', turnServer);
- pcConfig.iceServers.push({
- 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn,
- 'credential': turnServer.password
- });
- turnReady = true;
- }
- };
- xhr.open('GET', turnURL, true);
- xhr.send();
- }
- }
-
-
-
-
- if (location.hostname !== 'localhost') {
- requestTurn(
- `${window.location.origin}/corsProxy/${encodeURIComponent("https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913")}`
- );
- }
-
- const maybeStart = () => {
- console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
- if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
- console.log('>>>>>> creating peer connection');
- createPeerConnection();
- pc.addStream(localStream);
- isStarted = true;
- console.log('isInitiator', isInitiator);
- if (isInitiator) {
- doCall();
- }
- }
- };
-
- window.onbeforeunload = function () {
- sendMessage('bye');
- };
-
- /////////////////////////////////////////////////////////
-
- const createPeerConnection = () => {
- try {
- pc = new RTCPeerConnection(null);
- pc.onicecandidate = handleIceCandidate;
- pc.onaddstream = handleRemoteStreamAdded;
- pc.onremovestream = handleRemoteStreamRemoved;
- console.log('Created RTCPeerConnnection');
- } catch (e) {
- console.log('Failed to create PeerConnection, exception: ' + e.message);
- alert('Cannot create RTCPeerConnection object.');
- return;
- }
- }
-
- const handleIceCandidate = (event) => {
- console.log('icecandidate event: ', event);
- if (event.candidate) {
- sendMessage({
- type: 'candidate',
- label: event.candidate.sdpMLineIndex,
- id: event.candidate.sdpMid,
- candidate: event.candidate.candidate
- });
- } else {
- console.log('End of candidates.');
- }
- }
-
- const handleCreateOfferError = (event) => {
- console.log('createOffer() error: ', event);
- }
-
- const doCall = () => {
- console.log('Sending offer to peer');
- pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
- }
-
- const doAnswer = () => {
- console.log('Sending answer to peer.');
- pc.createAnswer().then(
- setLocalAndSendMessage,
- onCreateSessionDescriptionError
- );
- }
-
- const setLocalAndSendMessage = (sessionDescription) => {
- pc.setLocalDescription(sessionDescription);
- console.log('setLocalAndSendMessage sending message', sessionDescription);
- sendMessage(sessionDescription);
- }
-
- const onCreateSessionDescriptionError = (error) => {
- trace('Failed to create session description: ' + error.toString());
- }
-
-
-
- const handleRemoteStreamAdded = (event) => {
- console.log('Remote stream added.');
- remoteStream = event.stream;
- remoteVideo.srcObject = remoteStream;
- handlerUI();
-
- };
-
- const handleRemoteStreamRemoved = (event) => {
- console.log('Remote stream removed. Event: ', event);
- }
-}
-
-export function hangup() {
- console.log('Hanging up.');
- stop();
- sendMessage('bye');
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function stop() {
- isStarted = false;
- if (pc) {
- pc.close();
- }
- pc = null;
-}
-
-function handleRemoteHangup() {
- console.log('Session terminated.');
- stop();
- isInitiator = false;
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function sendMessage(message) {
- console.log('Client sending message: ', message);
- socket.emit('message', message, room);
-};
-
-export function refreshVideos() {
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
- if (localVideo) {
- localVideo.srcObject = localStream;
- }
- if (remoteVideo) {
- remoteVideo.srcObject = remoteStream;
- }
-
-} \ No newline at end of file
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index a9f7c085f..8ac502348 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -30,7 +30,7 @@ class Repl extends React.Component {
if (!script.compiled) {
this.executedCommands.push({ command: this.text, result: 'Compile Error' });
} else {
- const result = script.run({ makeInterface }, e => this.executedCommands.push({ command: this.text, result: e.message || e }));
+ const result = script.run({ makeInterface }, err => this.executedCommands.push({ command: this.text, result: err.message || err }));
result.success && this.executedCommands.push({ command: this.text, result: result.result });
}
this.text = '';
@@ -39,15 +39,13 @@ class Repl extends React.Component {
@computed
get commands() {
- return this.executedCommands.map(command => {
- return (
- <div style={{ marginTop: '5px' }}>
- <p>{command.command}</p>
- {/* <pre>{JSON.stringify(command.result, null, 2)}</pre> */}
- <pre>{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}</pre>
- </div>
- );
- });
+ return this.executedCommands.map(command => (
+ <div style={{ marginTop: '5px' }}>
+ <p>{command.command}</p>
+ {/* <pre>{JSON.stringify(command.result, null, 2)}</pre> */}
+ <pre>{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}</pre>
+ </div>
+ ));
}
render() {
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index c906dcc03..856cf6bd4 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,11 +1,12 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-console.log('ENTERED');
+
class Test extends React.Component {
render() {
return <div> HELLO WORLD </div>;
}
}
+
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<Test />);
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index ceff01fc2..f3295a6c6 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -1,14 +1,19 @@
+/* eslint-disable react/no-unescaped-entities */
+/* eslint-disable react/button-has-type */
+/* eslint-disable no-use-before-define */
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable no-redeclare */
import { action, configure, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOM from 'react-dom/client';
import { DocServer } from '../client/DocServer';
import { resolvedPorts } from '../client/util/CurrentUserUtils';
import { CompileScript } from '../client/util/Scripting';
import { EditableView } from '../client/views/EditableView';
import CursorField from '../fields/CursorField';
import { DateField } from '../fields/DateField';
-import { Doc, Field, FieldResult } from '../fields/Doc';
+import { Doc, Field, FieldType, FieldResult } from '../fields/Doc';
import { Id } from '../fields/FieldSymbols';
import { List } from '../fields/List';
import { RichTextField } from '../fields/RichTextField';
@@ -39,7 +44,7 @@ configure({
});
@observer
-class ListViewer extends React.Component<{ field: List<Field> }> {
+class ListViewer extends React.Component<{ field: List<FieldType> }> {
@observable
expanded = false;
@@ -86,14 +91,12 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
let content;
if (this.expanded) {
const keys = Object.keys(this.props.field);
- const fields = keys.map(key => {
- return (
- <div key={key}>
- <b>({key}): </b>
- <DebugViewer field={this.props.field[key]} setValue={value => applyToDoc(this.props.field, key, value)}></DebugViewer>
- </div>
- );
- });
+ const fields = keys.map(key => (
+ <div key={key}>
+ <b>({key}): </b>
+ <DebugViewer field={this.props.field[key]} setValue={value => applyToDoc(this.props.field, key, value)} />
+ </div>
+ ));
content = (
<div>
Document ({this.props.field[Id]})<div style={{ paddingLeft: '25px' }}>{fields}</div>
@@ -115,7 +118,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
class DebugViewer extends React.Component<{ field: FieldResult; setValue(value: string): boolean }> {
render() {
let content;
- const field = this.props.field;
+ const { field } = this.props;
if (field instanceof List) {
content = <ListViewer field={field} />;
} else if (field instanceof Doc) {
@@ -144,7 +147,7 @@ class Viewer extends React.Component {
private idToAdd: string = '';
@observable
- private fields: Field[] = [];
+ private fields: FieldType[] = [];
@action
inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -171,7 +174,7 @@ class Viewer extends React.Component {
<input value={this.idToAdd} onChange={this.inputOnChange} onKeyDown={this.onKeyDown} />
<div>
{this.fields.map((field, index) => (
- <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>
+ <DebugViewer field={field} key={index} setValue={() => false} />
))}
</div>
</>
@@ -181,10 +184,13 @@ class Viewer extends React.Component {
(async function () {
await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, 'viewer');
- ReactDOM.render(
- <div style={{ position: 'absolute', width: '100%', height: '100%' }}>
- <Viewer />
- </div>,
- document.getElementById('root')
- );
+ const root = document.getElementById('root');
+ if (root) {
+ const reactDom = ReactDOM.createRoot(root);
+ reactDom.render(
+ <div style={{ position: 'absolute', width: '100%', height: '100%' }}>
+ <Viewer />
+ </div>
+ );
+ }
})();
diff --git a/src/extensions/Extensions.ts b/src/extensions/Extensions.ts
new file mode 100644
index 000000000..5815527d1
--- /dev/null
+++ b/src/extensions/Extensions.ts
@@ -0,0 +1,9 @@
+import { Assign as ArrayAssign } from './Extensions_Array';
+import { Assign as StringAssign } from './Extensions_String';
+
+function AssignAllExtensions() {
+ ArrayAssign();
+ StringAssign();
+}
+
+export { AssignAllExtensions };
diff --git a/src/extensions/General/ExtensionsTypings.ts b/src/extensions/ExtensionsTypings.ts
index 370157ed0..d6ffd3be3 100644
--- a/src/extensions/General/ExtensionsTypings.ts
+++ b/src/extensions/ExtensionsTypings.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
interface Array<T> {
lastElement(): T;
}
@@ -5,4 +6,4 @@ interface Array<T> {
interface String {
removeTrailingNewlines(): string;
hasNewline(): boolean;
-} \ No newline at end of file
+}
diff --git a/src/extensions/ArrayExtensions.ts b/src/extensions/Extensions_Array.ts
index 8e125766d..a50fb330f 100644
--- a/src/extensions/ArrayExtensions.ts
+++ b/src/extensions/Extensions_Array.ts
@@ -8,12 +8,12 @@ export default class ArrayExtension {
}
assign() {
+ // eslint-disable-next-line no-extend-native
Object.defineProperty(Array.prototype, this.property, {
value: this.body,
- enumerable: false
+ enumerable: false,
});
}
-
}
/**
@@ -22,16 +22,16 @@ export default class ArrayExtension {
* Typescript will not recognize your new function.
*/
const extensions = [
- new ArrayExtension("lastElement", function () {
+ new ArrayExtension('lastElement', function () {
if (!this.length) {
return undefined;
}
return this[this.length - 1];
- })
+ }),
];
function Assign() {
extensions.forEach(extension => extension.assign());
}
-export { Assign }; \ No newline at end of file
+export { Assign };
diff --git a/src/extensions/StringExtensions.ts b/src/extensions/Extensions_String.ts
index 2c76e56c8..c95095f8e 100644
--- a/src/extensions/StringExtensions.ts
+++ b/src/extensions/Extensions_String.ts
@@ -1,17 +1,16 @@
+/* eslint-disable no-extend-native */
function Assign() {
-
String.prototype.removeTrailingNewlines = function () {
let sliced = this;
- while (sliced.endsWith("\n")) {
+ while (sliced.endsWith('\n')) {
sliced = sliced.substring(0, this.length - 1);
}
return sliced as string;
};
String.prototype.hasNewline = function () {
- return this.endsWith("\n");
+ return this.endsWith('\n');
};
-
}
-export { Assign }; \ No newline at end of file
+export { Assign };
diff --git a/src/extensions/General/Extensions.ts b/src/extensions/General/Extensions.ts
deleted file mode 100644
index 4b6d05d5f..000000000
--- a/src/extensions/General/Extensions.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Assign as ArrayAssign } from "../ArrayExtensions";
-import { Assign as StringAssign } from "../StringExtensions";
-
-function AssignAllExtensions() {
- ArrayAssign();
- StringAssign();
-}
-
-export { AssignAllExtensions }; \ No newline at end of file
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index 1e5222fb6..f0a851ce6 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -1,5 +1,5 @@
+import { serializable, date as serializrDate } from 'serializr';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, date } from 'serializr';
import { ObjectField } from './ObjectField';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -7,7 +7,7 @@ import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGloba
@scriptingGlobal
@Deserializable('date')
export class DateField extends ObjectField {
- @serializable(date())
+ @serializable(serializrDate())
readonly date: Date;
constructor(date: Date = new Date()) {
@@ -38,6 +38,7 @@ export class DateField extends ObjectField {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function d(...dateArgs: any[]) {
return new DateField(new (Date as any)(...dateArgs));
});
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 48214cf25..7b5e21ab7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,26 +1,22 @@
-import { saveAs } from 'file-saver';
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { alias, map, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
-import { LinkManager } from '../client/util/LinkManager';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
-import { undoable } from '../client/util/UndoManager';
-import { DocumentView } from '../client/views/nodes/DocumentView';
-import { decycle } from '../decycler/decycler';
-import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, Utils } from '../Utils';
-import { DateField } from './DateField';
+import { undoable, UndoManager } from '../client/util/UndoManager';
+import { ClientUtils, incrementTitleCopy } from '../ClientUtils';
import {
AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
} from './DocSymbols'; // prettier-ignore
import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { InkField, InkTool } from './InkField';
-import { List, ListFieldName } from './List';
+import { InkTool } from './InkField';
+import { List } from './List';
import { ObjectField } from './ObjectField';
import { PrefetchProxy, ProxyField } from './Proxy';
import { FieldId, RefField } from './RefField';
@@ -28,10 +24,8 @@ import { RichTextField } from './RichTextField';
import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
-import * as JSZip from 'jszip';
-import { FieldViewProps } from '../client/views/nodes/FieldView';
+
export const LinkedTo = '-linkedTo';
export namespace Field {
/**
@@ -43,9 +37,9 @@ export namespace Field {
* @returns string representation of the field
*/
export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
- const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
- const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const valFunc = (field: Field): string => {
+ const isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const valFunc = (field: FieldType): string => {
const res =
field instanceof ComputedField && showComputedValue
? field.value(doc)
@@ -61,9 +55,9 @@ export namespace Field {
.trim()
.replace(/^new List\((.*)\)$/, '$1');
};
- return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
+ return !Field.IsField(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield);
}
- export function toScriptString(field: Field) {
+ export function toScriptString(field: FieldType) {
switch (typeof field) {
case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
@@ -72,8 +66,8 @@ export namespace Field {
default: return field?.[ToScriptString]?.() ?? 'null';
} // prettier-ignore
}
- export function toJavascriptString(field: Field) {
- var rawjava = '';
+ export function toJavascriptString(field: FieldType) {
+ let rawjava = '';
switch (typeof field) {
case 'string':
@@ -82,12 +76,12 @@ export namespace Field {
break;
default: rawjava = field?.[ToJavascriptString]?.() ?? '';
} // prettier-ignore
- var script = rawjava;
+ let script = rawjava;
// this is a bit hacky, but we treat '^@' references to a published document
// as a kind of macro to include the content of those documents
Doc.MyPublishedDocs.forEach(doc => {
const regexMultilineFlag = 'm';
- const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[\(\)]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
+ const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[()]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
if (script.match(regex)) {
script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
@@ -95,23 +89,25 @@ export namespace Field {
});
return script;
}
- export function toString(field: Field) {
+ export function toString(field: FieldType) {
if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
return field?.[ToString]?.() || '';
}
- export function IsField(field: any): field is Field;
- export function IsField(field: any, includeUndefined: true): field is Field | undefined;
- export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+ export function IsField(field: any): field is FieldType;
+ export function IsField(field: any, includeUndefined: true): field is FieldType | undefined;
+ export function IsField(field: any, includeUndefined: boolean = false): field is FieldType | undefined {
return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
}
+ // eslint-disable-next-line @typescript-eslint/no-shadow
export function Copy(field: any) {
return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
}
+ UndoManager.SetFieldPrinter(toJavascriptString);
}
-export type Field = number | string | boolean | ObjectField | RefField;
+export type FieldType = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
-export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
+export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
/**
* Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
@@ -120,7 +116,9 @@ export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract
* If no default value is given, and the returned value is not undefined, it can be safely modified.
*/
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
const list = Cast(field, listSpec(Doc));
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
@@ -156,19 +154,24 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; im
// caches the document access permissions for the current user.
// this recursively updates all protos as well.
export function updateCachedAcls(doc: Doc) {
- if (!doc) return;
-
- const target = (doc as any)?.__fieldTuples ?? doc;
- const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
- Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
- if (Object.keys(permissions).length || doc[DocAcl]?.length) {
- runInAction(() => (doc[DocAcl] = permissions));
- }
+ if (doc) {
+ const target = (doc as any)?.__fieldTuples ?? doc;
+ const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail() ? { 'acl-Me': AclAdmin } : {};
+ Object.keys(target).forEach(key => {
+ key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl);
+ });
+ if (Object.keys(permissions).length || doc[DocAcl]?.length) {
+ runInAction(() => {
+ doc[DocAcl] = permissions;
+ });
+ }
- if (doc.proto instanceof Promise) {
- doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
- return doc.proto;
+ if (doc.proto instanceof Promise) {
+ doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+ return doc.proto;
+ }
}
+ return undefined;
}
@scriptingGlobal
@@ -178,7 +181,8 @@ export class Doc extends RefField {
@observable public static GuestDashboard: Doc | undefined = undefined;
@observable public static GuestTarget: Doc | undefined = undefined;
@observable public static GuestMobile: Doc | undefined = undefined;
- public static CurrentUserEmail: string = '';
+
+ public static AddLink: undefined | ((link: Doc, checkExists?: boolean) => void);
public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
@@ -274,9 +278,9 @@ export class Doc extends RefField {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
return {
- configurable: true, //TODO Should configurable be true?
+ configurable: true, // TODO Should configurable be true?
enumerable: true,
- value: 0, //() => target.__fieldTuples[prop])
+ value: 0, // () => target.__fieldTuples[prop])
};
},
deleteProperty: deleteProperty,
@@ -288,7 +292,8 @@ export class Doc extends RefField {
if (!id || forceSave) {
DocServer.CreateField(docProxy);
}
- return docProxy;
+ // eslint-disable-next-line no-constructor-return
+ return docProxy; // need to return the proxy from the constructor so that all our added fields will get called
}
[key: string]: FieldResult;
@@ -300,14 +305,16 @@ export class Doc extends RefField {
private set __fieldTuples(value) {
// called by deserializer to set all fields in one shot
this[FieldTuples] = value;
- for (const key in value) {
- const field = value[key];
- field !== undefined && (this[FieldKeys][key] = true);
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ Object.keys(value).forEach(key => {
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
+ const field = value[key];
+ field !== undefined && (this[FieldKeys][key] = true);
+ if (field instanceof ObjectField) {
+ field[Parent] = this[Self];
+ field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ }
}
- }
+ });
}
@observable private [FieldTuples]: any = {};
@@ -320,7 +327,7 @@ export class Doc extends RefField {
@observable public [Animation]: Opt<Doc> = undefined;
@observable public [Highlight]: boolean = false;
@observable public [Brushed]: boolean = false;
- @observable public [DocViews] = new ObservableSet<DocumentView>();
+ @observable public [DocViews] = new ObservableSet<any /* DocumentView */>();
private [Self] = this;
private [SelfProxy]: any;
@@ -329,7 +336,7 @@ export class Doc extends RefField {
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
public [Initializing]: boolean = false;
- public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
DocServer.UpdateField(this[Id], serverOp);
}
@@ -352,7 +359,7 @@ export class Doc extends RefField {
let renderFieldKey: any;
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
if (typeof layoutField === 'string') {
- renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1];
+ [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
} else {
return Cast(layoutField, Doc, null);
}
@@ -363,13 +370,11 @@ export class Doc extends RefField {
public async [HandleUpdate](diff: any) {
const set = diff.$set;
- const sameAuthor = this.author === Doc.CurrentUserEmail;
- if (set) {
- for (const key in set) {
- const fprefix = 'fields.';
- if (!key.startsWith(fprefix)) {
- continue;
- }
+ const sameAuthor = this.author === ClientUtils.CurrentUserEmail();
+ const fprefix = 'fields.';
+ Object.keys(set ?? {})
+ .filter(key => key.startsWith(fprefix))
+ .forEach(async key => {
const fKey = key.substring(fprefix.length);
const fn = async () => {
const value = await SerializationHelper.Deserialize(set[key]);
@@ -387,18 +392,16 @@ export class Doc extends RefField {
const writeMode = DocServer.getFieldWriteMode(fKey);
if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
+ // eslint-disable-next-line no-await-in-loop
await fn();
} else {
this[CachedUpdates][fKey] = fn;
}
- }
- }
+ });
const unset = diff.$unset;
- if (unset) {
- for (const key in unset) {
- if (!key.startsWith('fields.')) {
- continue;
- }
+ Object.keys(unset ?? {})
+ .filter(key => key.startsWith(fprefix))
+ .forEach(async key => {
const fKey = key.substring(7);
const fn = () => {
this[UpdatingFromServer] = true;
@@ -411,11 +414,11 @@ export class Doc extends RefField {
} else {
this[CachedUpdates][fKey] = fn;
}
- }
- }
+ });
}
}
+// eslint-disable-next-line no-redeclare
export namespace Doc {
export function SetContainer(doc: Doc, container: Doc) {
if (container !== Doc.MyRecentlyClosed) {
@@ -454,7 +457,7 @@ export namespace Doc {
return doc;
}
}
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ export function GetT<T extends FieldType>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
}
export function isTemplateDoc(doc: Doc) {
@@ -482,8 +485,8 @@ export namespace Doc {
// 2) if the data doc has the field, then it's written there.
// 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
//
- export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
- if (key.startsWith('_')) key = key.substring(1);
+ export async function SetInPlace(doc: Doc, keyIn: string, value: FieldType | undefined, defaultProto: boolean) {
+ const key = keyIn.startsWith('_') ? keyIn.substring(1) : keyIn;
const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
@@ -500,7 +503,6 @@ export namespace Doc {
}
return protos;
}
-
/**
* This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
* the values of the properties of a source object into the target.
@@ -512,17 +514,15 @@ export namespace Doc {
* @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
* to a potentially undefined field, where each entry in this mapping is optional.
*/
- export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
+ export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<FieldType>>>, skipUndefineds: boolean = false, isInitializing = false) {
isInitializing && (doc[Initializing] = true);
- for (const key in fields) {
- if (fields.hasOwnProperty(key)) {
- const value = fields[key];
- if (!skipUndefineds || value !== undefined) {
- // Do we want to filter out undefineds?
- doc[key] = value;
- }
+ Object.keys(fields).forEach(key => {
+ const value = (fields as any)[key];
+ if (!skipUndefineds || value !== undefined) {
+ // Do we want to filter out undefineds?
+ doc[key] = value;
}
- }
+ });
isInitializing && (doc[Initializing] = false);
return doc;
}
@@ -569,7 +569,7 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
- const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ const key = fieldKey || Doc.LayoutFieldKey(listDoc);
const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
const ind = list.indexOf(doc);
@@ -586,7 +586,7 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
- const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ const key = fieldKey || Doc.LayoutFieldKey(listDoc);
const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
if (!allowDuplicates) {
@@ -603,6 +603,7 @@ export namespace Doc {
if (reversed) list.splice(0, 0, doc);
else list.push(doc);
} else {
+ // eslint-disable-next-line no-lonely-if
if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc);
else list.splice(before ? ind : ind + 1, 0, doc);
}
@@ -634,7 +635,7 @@ export namespace Doc {
embedding.createdFrom = doc;
embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
!Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
- embedding.author = Doc.CurrentUserEmail;
+ embedding.author = ClientUtils.CurrentUserEmail();
return embedding;
}
@@ -642,7 +643,7 @@ export namespace Doc {
export function BestEmbedding(doc: Doc) {
const dataDoc = doc[DocData];
const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
- const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
+ const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
bestEmbedding && Doc.AddEmbedding(doc, doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -670,37 +671,39 @@ export namespace Doc {
await Promise.all(
Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
- const assignKey = (val: any) => (copy[key] = val);
+ const assignKey = (val: any) => {
+ copy[key] = val;
+ };
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (field: ObjectField) => {
+ const copyObjectField = async (objField: ObjectField) => {
const list = await Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)));
assignKey(new List<Doc>(clones));
} else {
- assignKey(ObjectField.MakeCopy(field));
- if (field instanceof RichTextField) {
- if (DocsInTextFieldIds.some(id => field.Data.includes(`"${id}":`))) {
+ assignKey(ObjectField.MakeCopy(objField));
+ if (objField instanceof RichTextField) {
+ if (DocsInTextFieldIds.some(id => objField.Data.includes(`"${id}":`))) {
const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g');
- const rawdocids = field.Data.match(docidsearch);
+ const rawdocids = objField.Data.match(docidsearch);
const docids = rawdocids?.map((str: string) =>
DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str)
.replace(/"/g, '')
.trim()
);
const results = docids && (await DocServer.GetRefFields(docids));
- const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key]));
- docs?.map(doc => doc && Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
- rtfs.push({ copy, key, field });
+ const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results[rkey]));
+ rdocs?.map(d => d && Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
+ rtfs.push({ copy, key, field: objField });
}
}
}
};
const docAtKey = doc[key];
if (key === 'author') {
- assignKey(Doc.CurrentUserEmail);
+ assignKey(ClientUtils.CurrentUserEmail());
} else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
@@ -709,7 +712,7 @@ export namespace Doc {
(key.includes('layout[') ||
key.startsWith('layout') || //
['embedContainer', 'annotationOn', 'proto'].includes(key) ||
- (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === Doc.CurrentUserEmail))
+ (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()))
) {
assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
} else {
@@ -722,7 +725,8 @@ export namespace Doc {
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
- debugger; //This shouldn't happen...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happen...
} else {
assignKey(field);
}
@@ -749,7 +753,7 @@ export namespace Doc {
visited.add(clone);
Object.keys(clone)
.filter(key => key !== 'cloneOf')
- .map(key => {
+ .forEach(key => {
const docAtKey = DocCast(clone[key]);
if (docAtKey && !Doc.IsSystem(docAtKey)) {
if (!Array.from(cloneMap.values()).includes(docAtKey)) {
@@ -768,16 +772,16 @@ export namespace Doc {
export async function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<string, Doc>();
const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
+ const clone = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
const repaired = new Set<Doc>();
const linkedDocs = Array.from(linkMap.values());
- linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true));
- rtfMap.map(({ copy, key, field }) => {
- const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+ linkedDocs.forEach(link => Doc.AddLink?.(link, true));
+ rtfMap.forEach(({ copy, key, field }) => {
+ const replacer = (match: any, attr: string, id: string /* , offset: any, string: any */) => {
const mapped = cloneMap.get(id);
return attr + '"' + (mapped ? mapped[Id] : id) + '"';
};
- const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+ const replacer2 = (match: any, href: string, id: string /* , offset: any, string: any */) => {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
@@ -786,86 +790,8 @@ export namespace Doc {
copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text);
});
const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
- clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, cloneTemplates, repaired));
- return { clone: copy, map: cloneMap, linkMap };
- }
-
- export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
- const { clone, map, linkMap } = await Doc.MakeClone(doc);
- const proms = new Set<string>();
- function replacer(key: any, value: any) {
- if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
- if (value?.__type === 'image') {
- const extension = value.url.replace(/.*\./, '');
- proms.add(value.url.replace('.' + extension, '_o.' + extension));
- return SerializationHelper.Serialize(new ImageField(value.url));
- }
- if (value?.__type === 'pdf') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new PdfField(value.url));
- }
- if (value?.__type === 'audio') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new AudioField(value.url));
- }
- if (value?.__type === 'video') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new VideoField(value.url));
- }
- if (
- value instanceof Doc ||
- value instanceof ScriptField ||
- value instanceof RichTextField ||
- value instanceof InkField ||
- value instanceof CsvField ||
- value instanceof WebField ||
- value instanceof DateField ||
- value instanceof ProxyField ||
- value instanceof ComputedField
- ) {
- return SerializationHelper.Serialize(value);
- }
- if (value instanceof Array && key !== ListFieldName && key !== InkField.InkDataFieldName) return { fields: value, __type: 'list' };
- return value;
- }
-
- const docs: { [id: string]: any } = {};
- const links: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
- Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
- const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
-
- const zip = new JSZip();
- var count = 0;
- const promArr = Array.from(proms)
- .filter(url => url?.startsWith('/files'))
- .map(url => url.replace('/', '')); // window.location.origin));
- console.log(promArr.length);
- if (!promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- } else
- promArr.forEach((url, i) => {
- // loading a file and add it in a zip file
- JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
- if (err) throw err; // or handle the error
- // // Generate a directory within the Zip file structure
- // const assets = zip.folder("assets");
- // assets.file(filename, data, {binary: true});
- const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
- zip.file(assetPathOnServer, data, { binary: true });
- console.log(' => ' + url);
- if (++count === promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- }
- });
- });
+ clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
+ return { clone, map: cloneMap, linkMap };
}
const _pendingMap = new Set<string>();
@@ -895,6 +821,7 @@ export namespace Doc {
if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
+ // eslint-disable-next-line no-param-reassign
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
@@ -954,7 +881,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
if (field instanceof Doc) {
if (key === 'myLinkDatabase') {
@@ -965,6 +892,7 @@ export namespace Doc {
}
}
} else if (cfield instanceof ComputedField) {
+ /* empty */
} else if (field instanceof ObjectField) {
if (field instanceof Doc) {
Doc.FindReferences(field, references, system);
@@ -981,7 +909,8 @@ export namespace Doc {
Doc.FindReferences(field.value, references, system);
}
} else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happend...
}
}
});
@@ -1000,7 +929,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
@@ -1011,7 +940,8 @@ export namespace Doc {
? new ProxyField(Doc.MakeCopy(doc[key] as any)) // copy the expanded render template
: ObjectField.MakeCopy(field);
} else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happend...
} else {
copy[key] = field;
}
@@ -1038,10 +968,12 @@ export namespace Doc {
delegate[Initializing] = true;
updateCachedAcls(delegate);
delegate.proto = doc;
- delegate.author = Doc.CurrentUserEmail;
+ delegate.author = ClientUtils.CurrentUserEmail();
Object.keys(doc)
.filter(key => key.startsWith('acl'))
- .forEach(key => (delegate[key] = doc[key]));
+ .forEach(key => {
+ delegate[key] = doc[key];
+ });
title && (delegate.title = title);
delegate[Initializing] = false;
if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
@@ -1054,7 +986,7 @@ export namespace Doc {
// (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
// This is appropriate if you're trying to create a document that behaves like all
// regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
- export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) {
+ export function MakeDelegateWithProto(doc: Doc /* , id?: string, title?: string */) {
const ndoc = Doc.ApplyTemplate(doc);
if (ndoc) {
Doc.GetProto(ndoc).isDataDoc = true;
@@ -1067,7 +999,7 @@ export namespace Doc {
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
const proto = new Doc();
- proto.author = Doc.CurrentUserEmail;
+ proto.author = ClientUtils.CurrentUserEmail();
const target = Doc.MakeDelegate(proto);
const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
@@ -1103,7 +1035,6 @@ export namespace Doc {
!keepFieldKey && (templateField.title = metadataFieldKey);
const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
- const templateCaptionValue = templateField.caption;
// move any data that the template field had been rendering over to the template doc so that things will still be rendered
// when the template field is adjusted to point to the new metadatafield key.
// note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
@@ -1123,7 +1054,7 @@ export namespace Doc {
// converts a document id to a url path on the server
export function globalServerPath(doc: Doc | string = ''): string {
- return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
+ return ClientUtils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
}
// converts a document id to a url path on the server
export function localServerPath(doc?: Doc): string {
@@ -1189,7 +1120,9 @@ export namespace Doc {
return manager._searchQuery;
}
export function SetSearchQuery(query: string) {
- runInAction(() => (manager._searchQuery = query));
+ runInAction(() => {
+ manager._searchQuery = query;
+ });
}
export function UserDoc(): Doc {
return manager._user_doc;
@@ -1201,12 +1134,13 @@ export namespace Doc {
return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
}
export function SetUserDoc(doc: Doc) {
+ // eslint-disable-next-line no-return-assign
return (manager._user_doc = doc);
}
- const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
- });
+ const isSearchMatchCache = computedFn((doc: Doc) =>
+ (brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined)); // prettier-ignore
export function IsSearchMatch(doc: Doc) {
return isSearchMatchCache(doc);
}
@@ -1252,11 +1186,16 @@ export namespace Doc {
return BrushDoc(doc, true);
}
export function UnBrushAllDocs() {
- Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
+ Array.from(brushManager.BrushedDoc).forEach(
+ action(doc => {
+ doc[Brushed] = false;
+ })
+ );
}
- let UnhighlightWatchers: (() => void)[] = [];
- export let UnhighlightTimer: any;
+ const UnhighlightWatchers: (() => void)[] = [];
+ let UnhighlightTimer: any;
+ export function IsUnhighlightTimerSet() { return UnhighlightTimer; } // prettier-ignore
export function AddUnHighlightWatcher(watcher: () => void) {
if (UnhighlightTimer) {
UnhighlightWatchers.push(watcher);
@@ -1270,9 +1209,9 @@ export namespace Doc {
highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
}
- export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentationEffect?: Doc) {
linkFollowUnhighlight();
- (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentationEffect));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
document.addEventListener('pointerdown', linkFollowUnhighlight);
if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
@@ -1282,31 +1221,34 @@ export namespace Doc {
}, 5000);
}
- export var highlightedDocs = new ObservableSet<Doc>();
+ export const highlightedDocs = new ObservableSet<Doc>();
export function IsHighlighted(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false;
return doc[Highlight] || doc[DocData][Highlight];
}
- export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentationEffect?: Doc) {
runInAction(() => {
highlightedDocs.add(doc);
doc[Highlight] = true;
- doc[Animation] = presentation_effect;
+ doc[Animation] = presentationEffect;
if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
// if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
highlightedDocs.add(doc[DocData]);
doc[DocData][Highlight] = true;
+ // want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
+ if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
}
});
}
/// if doc is defined, then it is unhighlighted, otherwise all highlighted docs are unhighlighted
- export function UnHighlightDoc(doc?: Doc) {
+ export function UnHighlightDoc(docs?: Doc) {
runInAction(() => {
- (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
+ (docs ? [docs] : Array.from(highlightedDocs)).forEach(doc => {
highlightedDocs.delete(doc);
highlightedDocs.delete(doc[DocData]);
doc[Highlight] = doc[DocData][Highlight] = false;
doc[Animation] = undefined;
+ if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
});
});
}
@@ -1380,6 +1322,7 @@ export namespace Doc {
const fields = childFilters[i].split(FilterSep); // split key:value:modifier
if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) {
+ // eslint-disable-next-line no-param-reassign
if (toggle) modifiers = 'remove';
else return;
}
@@ -1404,9 +1347,12 @@ export namespace Doc {
return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
}
}
+ return undefined;
}
export function assignDocToField(doc: Doc, field: string, id: string) {
- DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
+ DocServer.GetRefField(id).then(layout => {
+ layout instanceof Doc && (doc[field] = layout);
+ });
return id;
}
@@ -1426,20 +1372,6 @@ export namespace Doc {
});
}
- export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
- const style: { [key: string]: any } = {};
- const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
- const replacer = (match: any, expr: string, offset: any, string: any) => {
- // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
- };
- divKeys.map((prop: string) => {
- const p = (props as any)[prop];
- typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
- });
- return style;
- }
-
export function Paste(docids: string[], clone: boolean, addDocument: (doc: Doc | Doc[]) => boolean, ptx?: number, pty?: number, newPoint?: number[]) {
DocServer.GetRefFields(docids).then(async fieldlist => {
const list = Array.from(Object.values(fieldlist))
@@ -1495,6 +1427,7 @@ export namespace Doc {
case DocumentType.EQUATION: return 'calculator';
case DocumentType.SIMULATION: return 'rocket';
case DocumentType.CONFIG: return 'folder-closed';
+ default:
}
return 'question';
}
@@ -1505,7 +1438,7 @@ export namespace Doc {
// If they are not remapped, loading the file will overwrite any existing documents with those ids
//
export async function importDocument(file: File, remap = false) {
- const upload = Utils.prepend('/uploadDoc');
+ const upload = ClientUtils.prepend('/uploadDoc');
const formData = new FormData();
if (file) {
formData.append('file', file);
@@ -1513,12 +1446,13 @@ export namespace Doc {
const response = await fetch(upload, { method: 'POST', body: formData });
const json = await response.json();
if (json !== 'error') {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const docs = await DocServer.GetRefFields(json.docids as string[]);
const doc = DocCast(await DocServer.GetRefField(json.id));
const links = await DocServer.GetRefFields(json.linkids as string[]);
Array.from(Object.keys(links))
.map(key => links[key])
- .forEach(link => link instanceof Doc && LinkManager.Instance.addLink(link));
+ .forEach(link => link instanceof Doc && Doc.AddLink?.(link));
return doc;
}
}
@@ -1578,6 +1512,7 @@ export namespace Doc {
*/
export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
if (excludeEmptyObjects === undefined) {
+ // eslint-disable-next-line no-param-reassign
excludeEmptyObjects = true;
}
if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) {
@@ -1617,12 +1552,12 @@ export namespace Doc {
if (hasEntries || !excludeEmptyObjects) {
const resolved = target ?? new Doc();
if (hasEntries) {
- let result: Opt<Field>;
- Object.keys(object).map(key => {
+ Object.keys(object).forEach(key => {
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- if ((result = toField(object[key], excludeEmptyObjects, key))) {
+ const result = toField(object[key], excludeEmptyObjects, key);
+ if (result) {
resolved[key] = result;
}
});
@@ -1630,6 +1565,7 @@ export namespace Doc {
title && (resolved.title = title);
return resolved;
}
+ return undefined;
};
/**
@@ -1639,19 +1575,22 @@ export namespace Doc {
* @returns the list mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertList)
*/
- const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+ const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<FieldType>> => {
const target = new List();
- let result: Opt<Field>;
+ let result: Opt<FieldType>;
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+ list.forEach(item => {
+ (result = toField(item, excludeEmptyObjects)) && target.push(result);
+ });
if (target.length || !excludeEmptyObjects) {
return target;
}
+ return undefined;
};
- const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+ const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<FieldType> => {
if (data === null || data === undefined) {
return undefined;
}
@@ -1669,58 +1608,76 @@ export namespace Doc {
export function IdToDoc(id: string) {
return DocCast(DocServer.GetCachedRefField(id));
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function idToDoc(id: string): any {
return IdToDoc(id);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renameEmbedding(doc: any) {
return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getProto(doc: any) {
return Doc.GetProto(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getDocTemplate(doc?: any) {
return Doc.getDocTemplate(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getEmbedding(doc: any) {
return Doc.MakeEmbedding(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function copyField(field: any) {
return Field.Copy(field);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function docList(field: any) {
return DocListCast(field);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) {
return Doc.AddDocToList(doc, field, added);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
return Doc.SetInPlace(doc, field, value, false);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
return Doc.AreProtosEqual(doc1, doc2);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
return Doc.assignDocToField(doc, field, id);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
return Cast(doc, Doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function activePresentationItem() {
const curPres = Doc.ActivePresentation;
return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
Doc.setDocFilter(container, key, value, modifiers);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
Doc.setDocRangeFilter(container, key, range);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toJavascriptString(str: string) {
- return Field.toJavascriptString(str as Field);
+ return Field.toJavascriptString(str as FieldType);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function RtfField() {
return RichTextField.RTFfield();
});
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
index b67f0f7e9..536f5ce4c 100644
--- a/src/fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
@@ -1,7 +1,7 @@
+import { primitive, serializable } from 'serializr';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, primitive } from 'serializr';
-import { ObjectField } from './ObjectField';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
@Deserializable('html')
export class HtmlField extends ObjectField {
diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts
index 4d2badb68..33e5be7af 100644
--- a/src/fields/IconField.ts
+++ b/src/fields/IconField.ts
@@ -1,5 +1,5 @@
-import { Deserializable } from '../client/util/SerializationHelper';
import { serializable, primitive } from 'serializr';
+import { Deserializable } from '../client/util/SerializationHelper';
import { ObjectField } from './ObjectField';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index b3e01229a..c4f5f7a24 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -2,6 +2,7 @@ import { Bezier } from 'bezier-js';
import { alias, createSimpleSchema, list, object, serializable } from 'serializr';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
+import { PointData } from '../pen-gestures/GestureTypes';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
@@ -16,12 +17,6 @@ export enum InkTool {
PresentationPin = 'presentationpin',
}
-// Defines a point in an ink as a pair of x- and y-coordinates.
-export interface PointData {
- X: number;
- Y: number;
-}
-
export type Segment = Array<Bezier>;
// Defines an ink as an array of points.
@@ -62,10 +57,10 @@ const strokeDataSchema = createSimpleSchema({
'*': true,
});
+export const InkDataFieldName = '__inkData';
@Deserializable('ink')
export class InkField extends ObjectField {
- public static InkDataFieldName = '__inkData';
- @serializable(alias(InkField.InkDataFieldName, list(object(strokeDataSchema))))
+ @serializable(alias(InkDataFieldName, list(object(strokeDataSchema))))
readonly inkData: InkData;
constructor(data: InkData) {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index ec31f7dae..38c47d546 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,31 +1,28 @@
import { action, computed, makeObservable, observable } from 'mobx';
-import { alias, list, serializable } from 'serializr';
-import { DocServer } from '../client/DocServer';
+import { alias, list as serializrList, serializable } from 'serializr';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
-import { Field } from './Doc';
+import { Field, FieldType, StrListCast } from './Doc';
import { FieldTuples, Self, SelfProxy } from './DocSymbols';
import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefFields, ObjectField } from './ObjectField';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
-import { listSpec } from './Schema';
-import { Cast } from './Types';
import { containedFieldChangedHandler, deleteProperty, getter, setter } from './util';
-function toObjectField(field: Field) {
+function toObjectField(field: FieldType) {
return field instanceof RefField ? new ProxyField(field) : field;
}
-function toRealField(field: Field) {
+function toRealField(field: FieldType) {
return field instanceof ProxyField ? field.value : field;
}
-type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
+type StoredType<T extends FieldType> = T extends RefField ? ProxyField<T> : T;
export const ListFieldName = 'fields';
@Deserializable('list')
-class ListImpl<T extends Field> extends ObjectField {
+class ListImpl<T extends FieldType> extends ObjectField {
static listHandlers: any = {
/// Mutator methods
copyWithin() {
@@ -44,14 +41,14 @@ class ListImpl<T extends Field> extends ObjectField {
this[SelfProxy][FieldChanged]?.();
return field;
},
- push: action(function (this: ListImpl<any>, ...items: any[]) {
- items = items.map(toObjectField);
+ push: action(function (this: ListImpl<any>, ...itemsIn: any[]) {
+ const items = itemsIn.map(toObjectField);
const list = this[Self];
- const length = list.__fieldTuples.length;
+ const { length } = list.__fieldTuples;
for (let i = 0; i < items.length; i++) {
const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
+ // 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);
@@ -77,21 +74,21 @@ class ListImpl<T extends Field> extends ObjectField {
this[SelfProxy][FieldChanged]?.();
return res;
},
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ splice: action(function (this: any, start: number, deleteCount: number, ...itemsIn: any[]) {
this[Self].__realFields; // coerce retrieving entire array
- items = items.map(toObjectField);
+ const items = itemsIn.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
+ // 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 }[] = [];
+ const hintArray: { val: any; index: number }[] = [];
for (let i = start; i < start + deleteCount; i++) {
hintArray.push({ val: list.__fieldTuples[i], index: i });
}
@@ -107,13 +104,13 @@ class ListImpl<T extends Field> extends ObjectField {
);
return res.map(toRealField);
}),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
+ unshift(...itemsIn: any[]) {
+ const items = itemsIn.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
+ // 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);
@@ -131,9 +128,8 @@ class ListImpl<T extends Field> extends ObjectField {
includes(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
return this[Self].__realFields.includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
}
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
},
indexOf(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
@@ -151,9 +147,8 @@ class ListImpl<T extends Field> extends ObjectField {
lastIndexOf(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
}
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
},
slice(begin: number, end: number) {
this[Self].__realFields;
@@ -226,7 +221,7 @@ class ListImpl<T extends Field> extends ObjectField {
},
};
static listGetter(target: any, prop: string | symbol, receiver: any): any {
- if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+ if (Object.prototype.hasOwnProperty.call(ListImpl.listHandlers, prop)) {
return ListImpl.listHandlers[prop];
}
return getter(target, prop, receiver);
@@ -244,7 +239,7 @@ class ListImpl<T extends Field> extends ObjectField {
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
return {
- configurable: true, //TODO Should configurable be true?
+ configurable: true, // TODO Should configurable be true?
enumerable: true,
};
}
@@ -255,11 +250,13 @@ class ListImpl<T extends Field> extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
+ // eslint-disable-next-line no-use-before-define
+ this[SelfProxy] = list as any as List<FieldType>; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
this[SelfProxy].push(...fields);
}
- return list;
+ // eslint-disable-next-line no-constructor-return
+ return list; // need to return the proxy here, otherwise we don't get any of our list handler functions
}
[key: number]: T | (T extends RefField ? Promise<T> : never);
@@ -271,7 +268,7 @@ class ListImpl<T extends Field> extends ObjectField {
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
if (unrequested.length) {
- const batchPromise = DocServer.GetRefFields(unrequested.map(p => p.fieldId));
+ const batchPromise = ObjGetRefFields(unrequested.map(p => p.fieldId));
// as soon as we get the fields from the server, set all the list values in one
// action to generate one React dom update.
const allSetPromise = batchPromise.then(action(pfields => unrequested.map(toReq => toReq.setValue(pfields[toReq.fieldId]))));
@@ -282,20 +279,20 @@ class ListImpl<T extends Field> extends ObjectField {
return this[FieldTuples].map(toRealField);
}
- @serializable(alias(ListFieldName, list(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ @serializable(alias(ListFieldName, serializrList(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fieldTuples() {
return this[FieldTuples];
}
private set __fieldTuples(value) {
this[FieldTuples] = value;
- for (const key in value) {
- const item = value[key];
+ Object.keys(value).forEach(key => {
+ const item = value[Number(key)];
if (item instanceof ObjectField) {
item[Parent] = this[Self];
item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
}
- }
+ });
}
[Copy]() {
@@ -308,24 +305,24 @@ class ListImpl<T extends Field> extends ObjectField {
@observable
private [FieldTuples]: StoredType<T>[] = [];
private [Self] = this;
- private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
+ // eslint-disable-next-line no-use-before-define
+ private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references
- [ToJavascriptString]() {
- return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`;
- }
- [ToScriptString]() {
- return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
- }
- [ToString]() {
- return `[${(this as any).map((field: any) => Field.toString(field))}]`;
- }
+ [ToScriptString]() { return `new List(${this[ToJavascriptString]()})`; } // prettier-ignore
+ [ToJavascriptString]() { return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; } // prettier-ignore
+ [ToString]() { return `[${(this as any).map((field: any) => Field.toString(field))}]`; } // prettier-ignore
}
-export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
+
+// declare List as a type so you can use it in type declarations, e.g., { l: List, ...}
+export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
+// decalre List as a value so you can invoke 'new' on it, e.g., new List<Doc>()
+// eslint-disable-next-line no-redeclare
+export const List: { new <T extends FieldType>(fields?: T[]): List<T> } = ListImpl as any;
ScriptingGlobals.add('List', List);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function compareLists(l1: any, l2: any) {
- const L1 = Cast(l1, listSpec('string'), []);
- const L2 = Cast(l2, listSpec('string'), []);
+ const L1 = StrListCast(l1);
+ const L2 = StrListCast(l2);
return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
}, 'compare two lists');
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index e1b5b036c..231086262 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,26 +1,36 @@
-import { RefField } from './RefField';
-import { FieldChanged, Parent, Copy, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { Field } from './Doc';
+import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
+import { RefField } from './RefField';
export abstract class ObjectField {
// prettier-ignore
public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set';
- items: Field[] | undefined;
+ // eslint-disable-next-line no-use-before-define
+ items: FieldType[] | undefined;
length: number | undefined;
hint?: any }, serverOp?: any) => void;
+ // eslint-disable-next-line no-use-before-define
public [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
abstract [ToJavascriptString](): string;
abstract [ToScriptString](): string;
abstract [ToString](): string;
-}
-
-export namespace ObjectField {
- export function MakeCopy<T extends ObjectField>(field: T) {
+ static MakeCopy<T extends ObjectField>(field: T) {
return field?.[Copy]();
}
}
+export type FieldType = number | string | boolean | ObjectField | RefField; // bcz: hack for now .. must match the type definition in Doc.ts .. put here to avoid import cycles
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefField: (id: string, force?: boolean) => Promise<RefField | undefined>;
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefFields: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>;
+
+export function SetObjGetRefField(func: (id: string, force?: boolean) => Promise<RefField | undefined>) {
+ ObjGetRefField = func;
+}
+export function SetObjGetRefFields(func: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>) {
+ ObjGetRefFields = func;
+}
ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 820d9b6ff..4f8058ce4 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -21,7 +21,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
constructor(value?: T | string) {
super();
if (typeof value === 'string') {
- //this.cache = DocServer.GetCachedRefField(value) as any;
+ // this.cache = DocServer.GetCachedRefField(value) as any;
this.fieldId = value;
} else if (value) {
this.cache = { field: value, p: undefined };
@@ -29,7 +29,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
}
- [ToValue](doc: any) {
+ [ToValue](/* doc: any */) {
return ProxyField.toValue(this);
}
@@ -39,10 +39,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
[ToJavascriptString]() {
- return Field.toScriptString(this[ToValue](undefined)?.value);
+ return Field.toScriptString(this[ToValue]()?.value);
}
[ToScriptString]() {
- 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' ?
+ return Field.toScriptString(this[ToValue]()?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
}
[ToString]() {
return 'ProxyField';
@@ -59,7 +59,9 @@ export class ProxyField<T extends RefField> extends ObjectField {
return this._cache;
}
private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
- runInAction(() => (this._cache = { ...val }));
+ runInAction(() => {
+ this._cache = { ...val };
+ });
}
private failed = false;
@@ -78,7 +80,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
return this.cache.field ?? this.cache.p;
}
@computed get needsRequesting(): boolean {
- return !this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId) ? true : false;
+ return !!(!this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId));
}
setExternalValuePromise(externalValuePromise: Promise<any>) {
@@ -92,6 +94,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
}
+// eslint-disable-next-line no-redeclare
export namespace ProxyField {
let useProxy = true;
export function DisableProxyFields() {
@@ -115,9 +118,11 @@ export namespace ProxyField {
if (useProxy) {
return { value: value.value };
}
+ return undefined;
}
}
+// eslint-disable-next-line no-use-before-define
function prefetchValue(proxy: PrefetchProxy<RefField>) {
return proxy.value as any;
}
diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts
index 01828dd14..1ce81368a 100644
--- a/src/fields/RefField.ts
+++ b/src/fields/RefField.ts
@@ -1,7 +1,7 @@
-import { serializable, primitive, alias } from 'serializr';
+import { alias, primitive, serializable } from 'serializr';
import { Utils } from '../Utils';
-import { Id, HandleUpdate, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
import { afterDocDeserialize } from '../client/util/SerializationHelper';
+import { HandleUpdate, Id, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
export type FieldId = string;
export abstract class RefField {
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index b84a91709..d75d66bf8 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,21 +1,22 @@
import { AssertionError } from 'assert';
+import * as Color from 'color';
import { docs_v1 } from 'googleapis';
import { Fragment, Mark, Node } from 'prosemirror-model';
import { sinkListItem } from 'prosemirror-schema-list';
import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
-import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
-import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { ClientUtils, DashColor } from '../ClientUtils';
+import { Utils } from '../Utils';
import { DocServer } from '../client/DocServer';
-import { Docs, DocUtils } from '../client/documents/Documents';
import { Networking } from '../client/Network';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocUtils, Docs } from '../client/documents/Documents';
import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
import { schema } from '../client/views/nodes/formattedText/schema_rts';
-import { DashColor, Utils } from '../Utils';
import { Doc, Opt } from './Doc';
import { Id } from './FieldSymbols';
import { RichTextField } from './RichTextField';
import { Cast, StrCast } from './Types';
-import * as Color from 'color';
export namespace RichTextUtils {
const delimiter = '\n';
@@ -140,8 +141,8 @@ export namespace RichTextUtils {
inlineObjectMap.set(object.objectId!, {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
- url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client),
+ url: ClientUtils.prepend(_m.client),
+ agnostic: ClientUtils.prepend(agnostic.client),
});
}
}
@@ -401,7 +402,7 @@ export namespace RichTextUtils {
DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
linkDoc.link_anchor_2 = exported;
}
- url = Utils.shareUrl(exported[Id]);
+ url = ClientUtils.shareUrl(exported[Id]);
}
}
value = { url };
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index f5e64ae1f..7217bec37 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -1,5 +1,9 @@
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-redeclare */
+/* eslint-disable no-use-before-define */
import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types';
-import { Doc, Field } from './Doc';
+import { Doc, FieldType } from './Doc';
import { ObjectField } from './ObjectField';
import { RefField } from './RefField';
import { SelfProxy } from './DocSymbols';
@@ -12,6 +16,7 @@ type AllToInterface<T extends Interface[]> = {
export const emptySchema = createSchema({});
export const Document = makeInterface(emptySchema);
+// eslint-disable-next-line no-redeclare
export type Document = makeInterface<[typeof emptySchema]>;
export interface InterfaceFunc<T extends Interface[]> {
@@ -36,9 +41,10 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
if (prop in schema) {
const desc = prop === 'proto' ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
if (typeof desc === 'object' && 'defaultVal' in desc && 'type' in desc) {
- //defaultSpec
+ // defaultSpec
return Cast(field, desc.type, desc.defaultVal);
}
+ // eslint-disable-next-line no-prototype-builtins
if (typeof desc === 'function' && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) {
const doc = Cast(field, Doc);
if (doc === undefined) {
@@ -73,8 +79,8 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
get() {
return Cast(this.__doc[key], type as any);
},
- set(value) {
- value = Cast(value, type as any);
+ set(setValue) {
+ const value = Cast(setValue, type as any);
if (value !== undefined) {
this.__doc[key] = value;
return;
@@ -93,17 +99,18 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
};
}
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } {
return undefined as any;
- (schema as any).proto = Doc;
- return schema as any;
+ // (schema as any).proto = Doc;
+ // return schema as any;
}
-export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
- return { List: type as any }; //TODO Types
+export function listSpec<U extends ToConstructor<FieldType>>(type: U): ListSpec<ToType<U>> {
+ return { List: type as any }; // TODO Types
}
-export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+export function defaultSpec<T extends ToConstructor<FieldType>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
return {
type: type as any,
defaultVal,
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index fb4dc4e5b..0a8dd1d9e 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,9 +1,19 @@
+import { primitive, serializable } from 'serializr';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, primitive } from 'serializr';
+import { Copy, FieldChanged, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, FieldChanged, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
+
+export enum ColumnType {
+ Number,
+ String,
+ Boolean,
+ Date,
+ Image,
+ RTF,
+ Enumeration,
+ Any,
+}
export const PastelSchemaPalette = new Map<string, string>([
// ["pink1", "#FFB4E8"],
@@ -69,13 +79,14 @@ export class SchemaHeaderField extends ObjectField {
@serializable(primitive())
desc: boolean | undefined; // boolean determines sort order, undefined when no sort
+ // eslint-disable-next-line default-param-last
constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) {
super();
this.heading = heading;
this.color = color;
- this.type = type ? type : 0;
- this.width = width ? width : -1;
+ this.type = type ?? 0;
+ this.width = width ?? -1;
this.desc = desc;
this.collapsed = collapsed;
}
@@ -124,6 +135,8 @@ export class SchemaHeaderField extends ObjectField {
return `SchemaHeaderField`;
}
}
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function schemaHeaderField(heading: string, color: string, type: number, width: number, desc?: boolean, collapsed?: boolean) {
return new SchemaHeaderField(heading, color, type, width, desc, collapsed);
});
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8b51088b2..8a3787768 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,17 +1,16 @@
import { action, makeObservable, observable } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
-import { DocServer } from '../client/DocServer';
-import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { PropSchema, SKIP, createSimpleSchema, custom, map, object, primitive, serializable } from 'serializr';
import { numberRange } from '../Utils';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
+import { CompileScript, CompiledScript, ScriptOptions, Transformer } from '../client/util/Scripting';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Deserializable, autoObject } from '../client/util/SerializationHelper';
+import { Doc, Field, FieldType, FieldResult, Opt } from './Doc';
import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
import { List } from './List';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefField, ObjectField } from './ObjectField';
import { Cast, StrCast } from './Types';
-import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
function optional(propSchema: PropSchema) {
return custom(
@@ -44,7 +43,9 @@ const scriptSchema = createSimpleSchema({
originalScript: true,
});
-function finalizeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+function finalizeScript(scriptIn: ScriptField) {
+ const script = scriptIn;
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
@@ -54,11 +55,13 @@ function finalizeScript(script: ScriptField) {
if (!compset.compiled) {
throw new Error("Couldn't compile setter script");
}
- (script as any).setterscript = compset;
+ script.setterscript = compset;
}
return comp;
}
-async function deserializeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+async function deserializeScript(scriptIn: ScriptField) {
+ const script = scriptIn;
if (script.captures) {
const captured: any = {};
(script.script.options as ScriptOptions).capturedVariables = captured;
@@ -68,13 +71,16 @@ async function deserializeScript(script: ScriptField) {
const val = capture.split(':')[1];
if (val === 'true') captured[key] = true;
else if (val === 'false') captured[key] = false;
- else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+ else if (val.startsWith('ID->')) captured[key] = await ObjGetRefField(val.replace('ID->', ''));
else if (!isNaN(Number(val))) captured[key] = Number(val);
else captured[key] = val;
})
- ).then(() => ((script as any).script = finalizeScript(script)));
+ ).then(() => {
+ script.script = finalizeScript(script);
+ });
} else {
- (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
+ // eslint-disable-next-line no-use-before-define
+ script.script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
}
}
@@ -84,9 +90,9 @@ export class ScriptField extends ObjectField {
@serializable
readonly rawscript: string | undefined;
@serializable(object(scriptSchema))
- readonly script: CompiledScript;
+ script: CompiledScript;
@serializable(object(scriptSchema))
- readonly setterscript: CompiledScript | undefined;
+ setterscript: CompiledScript | undefined;
@serializable
@observable
_cachedResult: FieldResult = undefined;
@@ -131,6 +137,7 @@ export class ScriptField extends ObjectField {
[ToString]() {
return this.script.originalScript;
}
+ // eslint-disable-next-line default-param-last
public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {
return CompileScript(script, {
params: {
@@ -150,20 +157,23 @@ export class ScriptField extends ObjectField {
});
}
+ // eslint-disable-next-line default-param-last
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
+ // eslint-disable-next-line default-param-last
public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
- public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) {
+ public static CallGpt(queryTextIn: string, setVal: (val: FieldResult) => void, target: Doc) {
+ let queryText = queryTextIn;
if (typeof queryText === 'string' && setVal) {
while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) {
const fieldRef = queryText.split('(this.')[1].replace(/\).*/, '');
- queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field));
+ queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as FieldType));
}
setVal(`Chat Pending: ${queryText}`);
gptAPICall(queryText, GPTCallType.COMPLETION).then(result => {
@@ -198,24 +208,29 @@ export class ComputedField extends ScriptField {
}
_lastComputedResult: FieldResult;
- value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ??
- computedFn((doc: Doc) =>
- this.script.compiled &&
- this.script.run( {
- this: doc,
- //value: '',
- _setCacheResult_: this.setCacheResult,
- _last_: this._lastComputedResult,
- _readOnly_: true,
- },
- console.log
- ).result
- )(doc)
- ); // prettier-ignore
+ value = (doc: Doc) => {
+ this._lastComputedResult =
+ this._cachedResult ??
+ computedFn(() =>
+ this.script.compiled &&
+ this.script.run(
+ {
+ this: doc,
+ // value: '',
+ _setCacheResult_: this.setCacheResult,
+ _last_: this._lastComputedResult,
+ _readOnly_: true,
+ },
+ console.log
+ ).result
+ )(); // prettier-ignore
+ return this._lastComputedResult;
+ };
- [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore
+ [ToValue](doc: Doc) { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore
[Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
+ // eslint-disable-next-line default-param-last
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
@@ -225,7 +240,7 @@ export class ComputedField extends ScriptField {
public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
if (!doc[`${fieldKey}_indexed`]) {
- const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]);
+ const flist = new List<number>(numberRange(curTimecode + 1).map(() => undefined) as any as number[]);
flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -235,7 +250,7 @@ export class ComputedField extends ScriptField {
}
public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
if (!doc[`${fieldKey}_`]) {
- const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
+ const flist = new List<string>(numberRange(curTimecode + 1).map(() => undefined) as any as string[]);
flist[curTimecode] = StrCast(doc[fieldKey]);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -244,9 +259,9 @@ export class ComputedField extends ScriptField {
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
- if (doc[`${fieldKey}`] instanceof List) return;
+ if (doc[`${fieldKey}`] instanceof List) return undefined;
if (!doc[`${fieldKey}_indexed`]) {
- const flist = new List<Field>(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]);
+ const flist = new List<FieldType>(numberRange(curTimecode + 1).map(() => undefined) as any as FieldType[]);
flist[curTimecode] = Field.Copy(doc[fieldKey]);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -257,11 +272,13 @@ export class ComputedField extends ScriptField {
false,
{}
);
- return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined);
+ doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+ return doc[fieldKey];
}
}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function setIndexVal(list: any[], index: number, value: any) {
while (list.length <= index) list.push(undefined);
list[index] = value;
@@ -271,6 +288,7 @@ ScriptingGlobals.add(
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function getIndexVal(list: any[], index: number, defaultVal: Opt<number> = undefined) {
return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), defaultVal);
},
@@ -279,12 +297,14 @@ ScriptingGlobals.add(
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function makeScript(script: string) {
return ScriptField.MakeScript(script);
},
'returns the value at a given index of a list',
'(list: any[], index: number)'
);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) {
ScriptField.CallGpt(queryText, setVal, target);
}, 'calls chat gpt for the query string and then calls setVal with the result');
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 337e8ca21..57a310f6d 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,5 +1,5 @@
import { DateField } from './DateField';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { Doc, FieldType, FieldResult, Opt } from './Doc';
import { List } from './List';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
@@ -7,63 +7,69 @@ import { RichTextField } from './RichTextField';
import { ScriptField } from './ScriptField';
import { CsvField, ImageField, WebField } from './URLField';
+// eslint-disable-next-line no-use-before-define
+export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+
+export type DefaultFieldConstructor<T extends FieldType> = {
+ type: ToConstructor<T>;
+ defaultVal: T;
+};
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends FieldType> = { List: ToConstructor<T> };
+
+export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any);
+
export type ToType<T extends InterfaceValue> = T extends 'string'
? string
: T extends 'number'
- ? number
- : T extends 'boolean'
- ? boolean
- : T extends ListSpec<infer U>
- ? List<U>
- : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
- T extends DefaultFieldConstructor<infer _U>
- ? never
- : T extends { new (...args: any[]): List<Field> }
- ? never
- : T extends { new (...args: any[]): infer R }
- ? R
- : T extends (doc?: Doc) => infer R
- ? R
- : never;
-
-export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+ ? number
+ : T extends 'boolean'
+ ? boolean
+ : T extends ListSpec<infer U>
+ ? List<U>
+ : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ T extends DefaultFieldConstructor<infer _U>
+ ? never
+ : T extends { new (...args: any[]): List<FieldType> }
+ ? never
+ : T extends { new (...args: any[]): infer R }
+ ? R
+ : T extends (doc?: Doc) => infer R
+ ? R
+ : never;
+export interface Interface {
+ [key: string]: InterfaceValue;
+ // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
+}
export type ToInterface<T extends Interface> = {
[P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
};
-// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
-export type ListSpec<T extends Field> = { List: ToConstructor<T> };
-
-export type DefaultFieldConstructor<T extends Field> = {
- type: ToConstructor<T>;
- defaultVal: T;
-};
-
// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
+// TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
+export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T;
-export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any);
-//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
-export interface Interface {
- [key: string]: InterfaceValue;
- // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
-}
-export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
+export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
-export type CastCtor = ToConstructor<Field> | ListSpec<Field>;
+type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
+// eslint-disable-next-line no-redeclare
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
+// eslint-disable-next-line no-redeclare
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
if (field instanceof Promise) {
return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
}
if (field !== undefined && !(field instanceof Promise)) {
if (typeof ctor === 'string') {
+ // eslint-disable-next-line valid-typeof
if (typeof field === ctor) {
return field as ToType<T>;
}
@@ -116,18 +122,18 @@ export function ImageCast(field: FieldResult, defaultVal: ImageField | null = nu
return Cast(field, ImageField, defaultVal);
}
-type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
-
-export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
+export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+// eslint-disable-next-line no-redeclare
+export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
+// eslint-disable-next-line no-redeclare
+export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
return field instanceof Promise || field === undefined ? defaultValue : field;
}
export interface PromiseLike<T> {
then(callback: (field: Opt<T>) => void): void;
}
-export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
+export function PromiseValue<T extends FieldType>(field: FieldResult<T>): PromiseLike<Opt<T>> {
if (field instanceof Promise) return field as Promise<Opt<T>>;
return {
then(cb: (field: Opt<T>) => void) {
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 87334ad16..3a83e7ca0 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,18 +1,14 @@
+import { custom, serializable } from 'serializr';
+import { ClientUtils } from '../ClientUtils';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, custom } from 'serializr';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
-import { ToScriptString, ToString, Copy, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal } from '../client/util/ScriptingGlobals';
-import { Utils } from '../Utils';
function url() {
return custom(
- function (value: URL) {
- return value?.origin === window.location.origin ? value.pathname : value?.href;
- },
- function (jsonValue: string) {
- return new URL(jsonValue, window.location.origin);
- }
+ (value: URL) => (value?.origin === window.location.origin ? value.pathname : value?.href),
+ (jsonValue: string) => new URL(jsonValue, window.location.origin)
);
}
@@ -20,30 +16,34 @@ export abstract class URLField extends ObjectField {
@serializable(url())
readonly url: URL;
- constructor(url: string);
- constructor(url: URL);
- constructor(url: URL | string) {
+ constructor(urlVal: string);
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ constructor(urlVal: URL);
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ constructor(urlVal: URL | string) {
super();
- if (typeof url === 'string') {
- url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
- }
- this.url = url;
+ this.url =
+ typeof urlVal !== 'string'
+ ? urlVal // it's an URL
+ : urlVal.startsWith('http')
+ ? new URL(urlVal)
+ : new URL(urlVal, window.location.origin);
}
[ToScriptString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url?.href}")`;
}
[ToJavascriptString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url?.href}")`;
}
[ToString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return this.url.pathname;
}
return this.url?.href;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 1cacfe30c..c73689e1f 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -104,7 +104,7 @@ export const documentSchema = createSchema({
export const collectionSchema = createSchema({
childLayoutTemplate: Doc, // layout template to use to render children of a collecion
- childLayoutString: 'string', //layout string to use to render children of a collection
+ childLayoutString: 'string', // layout string to use to render children of a collection
childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
childDontRegisterViews: 'boolean', // whether views made of this document are registered so that they can be found when drawing links
onChildClick: ScriptField, // script to run for each child when its clicked
@@ -113,4 +113,5 @@ export const collectionSchema = createSchema({
});
export type Document = makeInterface<[typeof documentSchema]>;
+// eslint-disable-next-line no-redeclare
export const Document = makeInterface(documentSchema);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ad592391e..72b0ef721 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,12 +1,11 @@
-import { $mobx, action, observable, runInAction, trace, values } from 'mobx';
+import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { returnZero } from '../Utils';
+import { ClientUtils, returnZero } from '../ClientUtils';
import { DocServer } from '../client/DocServer';
-import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
-import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
+import { Doc, DocListCast, FieldType, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, DirectLinks, 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';
@@ -16,24 +15,44 @@ import { RichTextField } from './RichTextField';
import { SchemaHeaderField } from './SchemaHeaderField';
import { ComputedField } from './ScriptField';
import { DocCast, ScriptCast, StrCast } from './Types';
-import { BaseException } from 'pdfjs-dist/types/src/shared/util';
+
+/**
+ * These are the various levels of access a user can have to a document.
+ *
+ * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
+ * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
+ * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
+ * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
+ * None: the document is not shared with that user.
+ * Unset: Remove a sharing permission (eg., used )
+ */
+export enum SharingPermissions {
+ Admin = 'Admin',
+ Edit = 'Edit',
+ Augment = 'Augment',
+ View = 'View',
+ None = 'Not-Shared',
+}
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
-var tracing = false;
+// eslint-disable-next-line prefer-const
+let tracing = false;
export function TraceMobx() {
tracing && trace();
}
-const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+export const _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+
+const _setterImpl = action((target: any, prop: string | symbol | number, valueIn: any, receiver: any): boolean => {
if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') {
- target[prop] = value;
+ target[prop] = valueIn;
return true;
}
- value = value?.[SelfProxy] ?? value; // convert any Doc type values to Proxy's
+ let value = valueIn?.[SelfProxy] ?? valueIn; // 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])) {
@@ -50,6 +69,7 @@ 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;
+ // eslint-disable-next-line no-use-before-define
value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
}
if (curValue instanceof ObjectField) {
@@ -59,11 +79,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value);
+ // eslint-disable-next-line no-use-before-define
const effectiveAcl = GetEffectiveAcl(target);
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
- const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+ const sameAuthor = fromServer || receiver.author === ClientUtils.CurrentUserEmail();
const writeToDoc =
sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
const writeToServer =
@@ -95,7 +116,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
(!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent(
{
- redo: () => (receiver[prop] = value),
+ redo: () => {
+ receiver[prop] = value;
+ },
undo: () => {
const wasUpdate = receiver[UpdatingFromServer];
const wasForce = receiver[ForceServerWrite];
@@ -131,57 +154,16 @@ export function denormalizeEmail(email: string) {
return email.replace(/__/g, '.');
}
-/**
- * Copies parent's acl fields to the 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);
- }
- });
-}
-
-/**
- * These are the various levels of access a user can have to a document.
- *
- * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
- *
- * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
- *
- * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
- *
- * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
- *
- * None: the document is not shared with that user.
- *
- * Unset: Remove a sharing permission (eg., used )
- */
-export enum SharingPermissions {
- Admin = 'Admin',
- Edit = 'Edit',
- Augment = 'Augment',
- View = 'View',
- None = 'Not-Shared',
-}
-
// return acl from cache or cache the acl and return.
-const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
- return getEffectiveAcl(target, user);
-}, true);
+// eslint-disable-next-line no-use-before-define
+const getEffectiveAclCache = computedFn((target: any, user?: string) => getEffectiveAcl(target, user), true);
/**
* Calculates the effective access right to a document for the current user.
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
if (!target) return AclPrivate;
- if (target[UpdatingFromServer] || Doc.CurrentUserEmail === 'guest') return AclAdmin;
+ if (target[UpdatingFromServer] || ClientUtils.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)
}
@@ -191,10 +173,9 @@ export function GetPropAcl(target: any, prop: string | symbol | number) {
return GetEffectiveAcl(target);
}
-let cachedGroups = observable([] as string[]);
-const getCachedGroupByNameCache = computedFn(function (name: string) {
- return cachedGroups.includes(name);
-}, true);
+const cachedGroups = observable([] as string[]);
+const getCachedGroupByNameCache = computedFn((name: string) => cachedGroups.includes(name), true);
+
export function GetCachedGroupByName(name: string) {
return getCachedGroupByNameCache(name);
}
@@ -205,10 +186,10 @@ function getEffectiveAcl(target: any, user?: string): symbol {
const targetAcls = target[DocAcl];
if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
- const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
+ const userChecked = user || ClientUtils.CurrentUserEmail(); // if the current user is the author of the document / the current user is a member of the admin group
if (targetAcls && Object.keys(targetAcls).length) {
let effectiveAcl = AclPrivate;
- for (const [key, value] of Object.entries(targetAcls)) {
+ Object.entries(targetAcls).forEach(([key, value]) => {
// 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
@@ -217,7 +198,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
effectiveAcl = value as symbol;
}
}
- }
+ });
return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
}
@@ -236,9 +217,9 @@ function getEffectiveAcl(target: any, user?: string): symbol {
* @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, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
- const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
- if (!visited) visited = [] as Doc[];
+// eslint-disable-next-line default-param-last
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited: Doc[] = [], allowUpgrade?: boolean, layoutOnly = false) {
+ const selfKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
if (!target || visited.includes(target) || key === selfKey) return;
visited.push(target);
@@ -249,23 +230,21 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) {
// propagate ACLs to links, children, and annotations
- LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false));
+ dataDoc[DirectLinks].forEach(link => distributeAcls(key, acl, link, visited, !!allowUpgrade));
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
- distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+ distributeAcls(key, acl, d, visited, !!allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
- distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+ distributeAcls(key, acl, d, visited, !!allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
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);
- });
+ .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey]), visited, !!allowUpgrade));
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
@@ -285,27 +264,43 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
dataDocChanged && updateCachedAcls(dataDoc);
}
-export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+/**
+ * Copies parent's acl fields to the child
+ */
+export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
+ [...Object.keys(parent), ...(ClientUtils.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);
+ }
+ });
+}
+
/**
* sets a callback function to be called whenever a value is assigned to the specified field key.
* For example, this is used to "publish" documents with titles that start with '@'
* @param prop
- * @param setter
+ * @param propSetter
*/
-export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) {
- _propSetterCB.set(prop, setter);
+export function SetPropSetterCb(prop: string, propSetter: ((target: any, value: any) => void) | undefined) {
+ _propSetterCB.set(prop, propSetter);
}
//
// 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 {
- if (!in_prop) {
+export function setter(target: any, inProp: string | symbol | number, value: any, receiver: any): boolean {
+ if (!inProp) {
console.log('WARNING: trying to set an empty property. This should be fixed. ');
return false;
}
- let prop = in_prop;
- const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+ let prop = inProp;
+ const effectiveAcl = inProp === 'constructor' || typeof inProp === '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;
@@ -319,12 +314,24 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
}
if (target.__fieldTuples[prop] instanceof ComputedField) {
if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
- return ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
+ return !!ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success;
}
}
return _setter(target, prop, value, receiver);
}
+function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
+ const field = target.__fieldTuples[prop];
+ const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
+ if (value) return value.value;
+ if (field === undefined && !ignoreProto && prop !== 'proto') {
+ const proto = getFieldImpl(target, 'proto', proxy, true); // TODO tfs: instead of proxy we could use target[SelfProxy]... I don't which semantics we want or if it really matters
+ if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
+ return getFieldImpl(proto, prop, proxy, ignoreProto);
+ }
+ }
+ return field;
+}
export function getter(target: any, prop: string | symbol, proxy: any): any {
// prettier-ignore
switch (prop) {
@@ -336,6 +343,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
case $mobx: return target.__fieldTuples[prop];
case DocLayout: return target.__LAYOUT__;
case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
+ // eslint-disable-next-line no-fallthrough
default :
if (typeof prop === 'symbol') return target[prop];
if (prop.startsWith('isMobX')) return target[prop];
@@ -343,23 +351,11 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
if (GetEffectiveAcl(target) === AclPrivate && prop !== 'author') return undefined;
}
- const layout_prop = prop.startsWith('_') ? prop.substring(1) : undefined;
- if (layout_prop && target.__LAYOUT__) return target.__LAYOUT__[layout_prop];
- return getFieldImpl(target, layout_prop ?? prop, proxy);
+ const layoutProp = prop.startsWith('_') ? prop.substring(1) : undefined;
+ if (layoutProp && target.__LAYOUT__) return target.__LAYOUT__[layoutProp];
+ return getFieldImpl(target, layoutProp ?? prop, proxy);
}
-function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
- const field = target.__fieldTuples[prop];
- const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
- if (value) return value.value;
- if (field === undefined && !ignoreProto && prop !== 'proto') {
- const proto = getFieldImpl(target, 'proto', proxy, true); //TODO tfs: instead of proxy we could use target[SelfProxy]... I don't which semantics we want or if it really matters
- if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
- return getFieldImpl(proto, prop, proxy, ignoreProto);
- }
- }
- return field;
-}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, target[SelfProxy], ignoreProto);
}
@@ -382,10 +378,10 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
// 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) {
+export function containedFieldChangedHandler(container: List<FieldType> | 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)) });
+ return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any } /* , dummyServerOp?: any */) => {
+ const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item)) });
// prettier-ignore
const serverOp = diff?.op === '$addToSet'
? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
@@ -401,8 +397,8 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
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));
+ // 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(() => {
@@ -416,7 +412,7 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
});
lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
}),
- prop: 'add ' + diff.items?.length + ' items to list',
+ prop: 'add ' + (diff.items?.length ?? 0) + ' items to list',
},
diff?.items
);
@@ -447,12 +443,14 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
});
lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
},
- prop: 'remove ' + diff.items?.length + ' items from list',
+ prop: 'remove ' + (diff.items?.length ?? 0) + ' 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));
+ const setFieldVal = (val: FieldType | undefined) => {
+ container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as FieldType);
+ };
UndoManager.AddEvent(
{
redo: () => {
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index da01e099c..7a1e35636 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,9 +1,11 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as rp from 'request-promise';
-import { Utils } from '../Utils';
+import { ClientUtils } from '../ClientUtils';
import { DocServer } from '../client/DocServer';
import { Networking } from '../client/Network';
import { Docs } from '../client/documents/Documents';
@@ -13,8 +15,9 @@ import { List } from '../fields/List';
import { listSpec } from '../fields/Schema';
import { Cast } from '../fields/Types';
import './ImageUpload.scss';
-import { MobileInterface } from './MobileInterface';
+
const { DFLT_IMAGE_NATIVE_DIM } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore
+
export interface ImageUploadProps {
Document: Doc; // Target document for upload (upload location)
}
@@ -24,28 +27,30 @@ const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
@observer
export class Uploader extends React.Component<ImageUploadProps> {
- @observable error: string = '';
@observable nm: string = 'Choose files'; // Text of 'Choose Files' button
@observable process: string = ''; // Current status of upload
+ @observable private dialogueBoxOpacity = 1;
onClick = async () => {
try {
+ // eslint-disable-next-line react/destructuring-assignment
const col = this.props.Document;
await Docs.Prototypes.initialize();
const imgPrev = document.getElementById('img_preview');
this.setOpacity(1, '1'); // Slab 1
- if (imgPrev) {
- const files: FileList | null = inputRef.current!.files;
+ if (imgPrev && inputRef.current) {
+ const { files } = inputRef.current;
this.setOpacity(2, '1'); // Slab 2
if (files && files.length !== 0) {
this.process = 'Uploading Files';
for (let index = 0; index < files.length; ++index) {
const file = files[index];
+ // eslint-disable-next-line no-await-in-loop
const res = await Networking.UploadFilesToServer({ file });
this.setOpacity(3, '1'); // Slab 3
// For each item that the user has selected
res.map(async ({ result }) => {
- const name = file.name;
+ const { name } = file;
if (result instanceof Error) {
return;
}
@@ -62,17 +67,17 @@ export class Uploader extends React.Component<ImageUploadProps> {
doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name });
}
this.setOpacity(4, '1'); // Slab 4
- const res = await rp.get(Utils.prepend('/getUserDocumentIds'));
- if (!res) {
+ const docidsRes = await rp.get(ClientUtils.prepend('/getUserDocumentIds'));
+ if (!docidsRes) {
throw new Error('No user id returned');
}
- const field = await DocServer.GetRefField(JSON.parse(res).userDocumentId);
+ const field = await DocServer.GetRefField(JSON.parse(docidsRes).userDocumentId);
let pending: Opt<Doc>;
if (field instanceof Doc) {
pending = col;
}
if (pending) {
- const data = await Cast(pending.data, listSpec(Doc));
+ const data = Cast(pending.data, listSpec(Doc));
if (data) data.push(doc);
else pending.data = new List([doc]);
this.setOpacity(5, '1'); // Slab 5
@@ -93,22 +98,49 @@ export class Uploader extends React.Component<ImageUploadProps> {
setTimeout(this.clearUpload, 3000);
}
} catch (error) {
- this.error = JSON.stringify(error);
+ console.log(JSON.stringify(error));
}
};
+ // Returns the upload interface for mobile
+ private get uploadInterface() {
+ return (
+ <div className="imgupload_cont">
+ <div className="closeUpload" onClick={() => this.closeUpload()}>
+ <FontAwesomeIcon icon="window-close" size="lg" />
+ </div>
+ <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: '130' }} />
+ <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== 'Choose files' ? 'active' : ''}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple />
+ <label className="file" id="label" htmlFor="input_image_file">
+ {this.nm}
+ </label>
+ <div className="upload_label" onClick={this.onClick}>
+ Upload
+ </div>
+ <img id="img_preview" src="" alt="" />
+ <div className="loadingImage">
+ <div className="loadingSlab" id="slab1" />
+ <div className="loadingSlab" id="slab2" />
+ <div className="loadingSlab" id="slab3" />
+ <div className="loadingSlab" id="slab4" />
+ <div className="loadingSlab" id="slab5" />
+ <div className="loadingSlab" id="slab6" />
+ <div className="loadingSlab" id="slab7" />
+ </div>
+ <p className="status">{this.process}</p>
+ </div>
+ );
+ }
+
// Updates label after a files is selected (so user knows a file is uploaded)
inputLabel = async () => {
- const files: FileList | null = inputRef.current!.files;
- await files;
+ const files: FileList | null = await inputRef.current!.files;
if (files && files.length === 1) {
this.nm = files[0].name;
} else if (files && files.length > 1) {
this.nm = files.length.toString() + ' files selected';
}
- };
-
- // Loops through load icons, and resets buttons
+ }; // Loops through load icons, and resets buttons
@action
clearUpload = () => {
for (let i = 1; i < 8; i++) {
@@ -125,7 +157,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
// Clears the upload and closes the upload menu
closeUpload = () => {
this.clearUpload();
- MobileInterface.Instance.toggleUpload();
};
// Handles the setting of the loading bar
@@ -134,40 +165,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
if (slab) slab.style.opacity = opacity;
};
- // Returns the upload interface for mobile
- private get uploadInterface() {
- return (
- <div className="imgupload_cont">
- <div className="closeUpload" onClick={() => this.closeUpload()}>
- <FontAwesomeIcon icon="window-close" size={'lg'} />
- </div>
- <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: '130' }} />
- <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== 'Choose files' ? 'active' : ''}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
- <label className="file" id="label" htmlFor="input_image_file">
- {this.nm}
- </label>
- <div className="upload_label" onClick={this.onClick}>
- Upload
- </div>
- <img id="img_preview" src=""></img>
- <div className="loadingImage">
- <div className="loadingSlab" id="slab1" />
- <div className="loadingSlab" id="slab2" />
- <div className="loadingSlab" id="slab3" />
- <div className="loadingSlab" id="slab4" />
- <div className="loadingSlab" id="slab5" />
- <div className="loadingSlab" id="slab6" />
- <div className="loadingSlab" id="slab7" />
- </div>
- <p className="status">{this.process}</p>
- </div>
- );
- }
-
- @observable private dialogueBoxOpacity = 1;
- @observable private overlayOpacity = 0.4;
-
render() {
- return <MainViewModal contents={this.uploadInterface} isDisplayed={true} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
+ return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.closeUpload} />;
}
}
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 23e19585a..6babd2f39 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { DocServer } from '../client/DocServer';
import { DragManager } from '../client/util/DragManager';
import { Doc } from '../fields/Doc';
-import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { Gestures } from '../pen-gestures/GestureTypes';
import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
import './MobileInkOverlay.scss';
@@ -76,11 +76,11 @@ export default class MobileInkOverlay extends React.Component {
const target = document.elementFromPoint(this._x + 10, this._y + 10);
target?.dispatchEvent(
- new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+ new CustomEvent<GestureEvent>('dashOnGesture', {
bubbles: true,
detail: {
points: points,
- gesture: GestureUtils.Gestures.Stroke,
+ gesture: Gestures.Stroke,
bounds: B,
},
})
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index dc4af8303..b355e70a7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -104,7 +104,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../ClientUtils';
import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
import { Docs, DocumentOptions } from '../client/documents/Documents';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
@@ -857,7 +857,7 @@ ScriptingGlobals.add(function switchToMobilePresentation() {
return MobileInterface.Instance.setupDefaultPresentation();
}, 'opens the presentation on Dash Mobile');
ScriptingGlobals.add(function openMobileSettings() {
- return SettingsManager.Instance.open();
+ return SettingsManager.Instance.openMgr();
}, 'opens settings on Dash Mobile');
// Other global functions for mobile
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index dc3a73def..07839b6f6 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -3,7 +3,7 @@ import * as ReactDOM from 'react-dom';
import { DocServer } from '../client/DocServer';
import { Docs } from '../client/documents/Documents';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { AssignAllExtensions } from '../extensions/General/Extensions';
+import { AssignAllExtensions } from '../extensions/Extensions';
import { MobileInterface } from './MobileInterface';
AssignAllExtensions();
diff --git a/src/pen-gestures/GestureTypes.ts b/src/pen-gestures/GestureTypes.ts
new file mode 100644
index 000000000..d86562580
--- /dev/null
+++ b/src/pen-gestures/GestureTypes.ts
@@ -0,0 +1,16 @@
+export enum Gestures {
+ Line = 'line',
+ Stroke = 'stroke',
+ Scribble = 'scribble',
+ Text = 'text',
+ Triangle = 'triangle',
+ Circle = 'circle',
+ Rectangle = 'rectangle',
+ Arrow = 'arrow',
+}
+
+// Defines a point in an ink as a pair of x- and y-coordinates.
+export interface PointData {
+ X: number;
+ Y: number;
+}
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 41917aac9..28323d62f 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,32 +1,32 @@
import { Rect } from 'react-measure';
-import { PointData } from '../fields/InkField';
+import { Gestures, PointData } from './GestureTypes';
import { NDollarRecognizer } from './ndollar';
export namespace GestureUtils {
export class GestureEvent {
- constructor(readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, readonly text?: any) {}
+ readonly gesture: Gestures;
+ readonly points: PointData[];
+ readonly bounds: Rect;
+ readonly text?: any;
+
+ constructor(gesture: Gestures, points: PointData[], bounds: Rect, text?: any) {
+ this.gesture = gesture;
+ this.points = points;
+ this.bounds = bounds;
+ this.text = text;
+ }
}
export interface GestureEventDisposer {
(): void;
}
+ // eslint-disable-next-line no-undef
export function MakeGestureTarget(element: HTMLElement, func: (e: Event, ge: GestureEvent) => void): GestureEventDisposer {
const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
element.addEventListener('dashOnGesture', handler);
return () => element.removeEventListener('dashOnGesture', handler);
}
- export enum Gestures {
- Line = 'line',
- Stroke = 'stroke',
- Scribble = 'scribble',
- Text = 'text',
- Triangle = 'triangle',
- Circle = 'circle',
- Rectangle = 'rectangle',
- Arrow = 'arrow',
- }
-
export const GestureRecognizer = new NDollarRecognizer(false);
}
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 3ee9506cb..5f1ea00dc 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -1,4 +1,5 @@
-import { GestureUtils } from './GestureUtils';
+/* eslint-disable no-use-before-define */
+import { Gestures } from './GestureTypes';
/**
* The $N Multistroke Recognizer (JavaScript version)
@@ -69,20 +70,34 @@ import { GestureUtils } from './GestureUtils';
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
- **/
+ * */
//
// Point class
//
export class Point {
- constructor(public X: number, public Y: number) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public X: number,
+ public Y: number
+ ) {
+ /* empty */
+ }
}
//
// Rectangle class
//
export class Rectangle {
- constructor(public X: number, public Y: number, public Width: number, public Height: number) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public X: number,
+ public Y: number,
+ public Width: number,
+ public Height: number
+ ) {
+ /* empty */
+ }
}
//
@@ -93,7 +108,11 @@ export class Unistroke {
public StartUnitVector: Point;
public Vector: number[];
- constructor(public Name: string, useBoundedRotationInvariance: boolean, points: Point[]) {
+ constructor(
+ public Name: string,
+ useBoundedRotationInvariance: boolean,
+ points: Point[]
+ ) {
this.Points = Resample(points, NumPoints);
const radians = IndicativeAngle(this.Points);
this.Points = RotateBy(this.Points, -radians);
@@ -121,15 +140,15 @@ export class Multistroke {
this.NumStrokes = strokes.length; // number of individual strokes
const order = new Array(strokes.length); // array of integer indices
- for (var i = 0; i < strokes.length; i++) {
+ for (let i = 0; i < strokes.length; i++) {
order[i] = i; // initialize
}
- const orders = new Array(); // array of integer arrays
- HeapPermute(strokes.length, order, /*out*/ orders);
+ const orders = [] as any[]; // array of integer arrays
+ HeapPermute(strokes.length, order, /* out */ orders);
const unistrokes = MakeUnistrokes(strokes, orders); // returns array of point arrays
this.Unistrokes = new Array(unistrokes.length); // unistrokes for this multistroke
- for (var j = 0; j < unistrokes.length; j++) {
+ for (let j = 0; j < unistrokes.length; j++) {
this.Unistrokes[j] = new Unistroke(this.Name, useBoundedRotationInvariance, unistrokes[j]);
}
}
@@ -139,7 +158,14 @@ export class Multistroke {
// Result class
//
export class Result {
- constructor(public Name: string, public Score: any, public Time: any) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public Name: string,
+ public Score: any,
+ public Time: any
+ ) {
+ /* empty */
+ }
}
//
@@ -172,46 +198,42 @@ export class NDollarRecognizer {
//
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Rectangle,
+ Gestures.Rectangle,
useBoundedRotationInvariance,
- new Array(
- new Array(
- new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200),
- new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230),
- new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160),
- new Point(106, 146), //new Point(80, 150), new Point(50, 146),
- new Point(30, 143)
- )
- )
+ new Array([
+ new Point(30, 146), // new Point(29, 160), new Point(30, 180), new Point(31, 200),
+ new Point(30, 222), // new Point(50, 219), new Point(70, 225), new Point(90, 230),
+ new Point(106, 225), // new Point(100, 200), new Point(106, 180), new Point(110, 160),
+ new Point(106, 146), // new Point(80, 150), new Point(50, 146),
+ new Point(30, 143),
+ ])
)
);
- this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))));
+ this.Multistrokes.push(new Multistroke(Gestures.Line, useBoundedRotationInvariance, [[new Point(12, 347), new Point(119, 347)]]));
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Triangle, // equilateral
+ Gestures.Triangle, // equilateral
useBoundedRotationInvariance,
- new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
+ new Array([new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)])
)
);
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Circle,
+ Gestures.Circle,
useBoundedRotationInvariance,
- new Array(
- new Array(
- new Point(200, 250),
- new Point(240, 230),
- new Point(248, 210),
- new Point(248, 190),
- new Point(240, 170),
- new Point(200, 150),
- new Point(160, 170),
- new Point(151, 190),
- new Point(151, 210),
- new Point(160, 230),
- new Point(201, 250)
- )
- )
+ new Array([
+ new Point(200, 250),
+ new Point(240, 230),
+ new Point(248, 210),
+ new Point(248, 190),
+ new Point(240, 170),
+ new Point(200, 150),
+ new Point(160, 170),
+ new Point(151, 190),
+ new Point(151, 210),
+ new Point(160, 230),
+ new Point(201, 250),
+ ])
)
);
NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes
@@ -292,20 +314,21 @@ export class NDollarRecognizer {
const points = CombineStrokes(strokes); // make one connected unistroke from the given strokes
const candidate = new Unistroke('', useBoundedRotationInvariance, points);
- var u = -1;
- var b = +Infinity;
+ let u = -1;
+ let b = +Infinity;
for (
- var i = 0;
+ let i = 0;
i < this.Multistrokes.length;
i++ // for each multistroke template
) {
if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) {
// optional -- only attempt match when same # of component strokes
- for (const unistroke of this.Multistrokes[i].Unistrokes) {
+ // eslint-disable-next-line no-loop-func
+ this.Multistrokes[i].Unistrokes.forEach(unistroke => {
// for each unistroke within this multistroke
if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) {
// strokes start in the same direction
- var d;
+ let d;
if (useProtractor) {
d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor
} else {
@@ -316,7 +339,7 @@ export class NDollarRecognizer {
u = i; // multistroke owner of unistroke
}
}
- }
+ });
}
}
const t1 = Date.now();
@@ -325,12 +348,12 @@ export class NDollarRecognizer {
AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
- var num = 0;
- for (const multistroke of this.Multistrokes) {
+ let num = 0;
+ this.Multistrokes.forEach(multistroke => {
if (multistroke.Name === name) {
num++;
}
- }
+ });
return num;
};
@@ -343,11 +366,11 @@ export class NDollarRecognizer {
//
// Private helper functions from here on down
//
-function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
+function HeapPermute(n: number, order: any[], /* out */ orders: any[]) {
if (n === 1) {
orders[orders.length] = order.slice(); // append copy
} else {
- for (var i = 0; i < n; i++) {
+ for (let i = 0; i < n; i++) {
HeapPermute(n - 1, order, orders);
if (n % 2 === 1) {
// swap 0, n-1
@@ -364,47 +387,44 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
}
}
-function MakeUnistrokes(strokes: any, orders: any) {
- const unistrokes = new Array(); // array of point arrays
- for (const order of orders) {
+function MakeUnistrokes(strokes: any, orders: any[]) {
+ const unistrokes = [] as any[]; // array of point arrays
+ orders.forEach(order => {
for (
- var b = 0;
- b < Math.pow(2, order.length);
+ let b = 0;
+ b < order.length ** 2;
b++ // use b's bits for directions
) {
- const unistroke = new Array(); // array of points
- for (var i = 0; i < order.length; i++) {
- var pts;
+ const unistroke = [] as any[]; // array of points
+ for (let i = 0; i < order.length; i++) {
+ let pts;
+ // eslint-disable-next-line no-bitwise
if (((b >> i) & 1) === 1) {
// is b's bit at index i on?
pts = strokes[order[i]].slice().reverse(); // copy and reverse
} else {
pts = strokes[order[i]].slice(); // copy
}
- for (const point of pts) {
+ pts.forEach((point: any) => {
unistroke[unistroke.length] = point; // append points
- }
+ });
}
unistrokes[unistrokes.length] = unistroke; // add one unistroke to set
}
- }
+ });
return unistrokes;
}
-function CombineStrokes(strokes: any) {
- const points = new Array();
- for (const stroke of strokes) {
- for (const { X, Y } of stroke) {
- points[points.length] = new Point(X, Y);
- }
- }
+function CombineStrokes(strokes: any[]) {
+ const points: Point[] = [];
+ strokes.forEach(stroke => stroke.forEach((X: any, Y: any) => points.push(new Point(X, Y))));
return points;
}
function Resample(points: any, n: any) {
const I = PathLength(points) / (n - 1); // interval length
- var D = 0.0;
+ let D = 0.0;
const newpoints = new Array(points[0]);
- for (var i = 1; i < points.length; i++) {
+ for (let i = 1; i < points.length; i++) {
const d = Distance(points[i - 1], points[i]);
if (D + d >= I) {
const qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
@@ -425,55 +445,55 @@ function IndicativeAngle(points: any) {
const c = Centroid(points);
return Math.atan2(c.Y - points[0].Y, c.X - points[0].X);
}
-function RotateBy(points: any, radians: any) {
+function RotateBy(points: Point[], radians: any) {
// rotates points around centroid
const c = Centroid(points);
const cos = Math.cos(radians);
const sin = Math.sin(radians);
- const newpoints = new Array();
- for (const point of points) {
+ const newpoints: Point[] = [];
+ points.forEach(point => {
const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X;
const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y;
- newpoints[newpoints.length] = new Point(qx, qy);
- }
+ newpoints.push(new Point(qx, qy));
+ });
return newpoints;
}
function ScaleDimTo(points: any, size: any, ratio1D: any) {
// scales bbox uniformly for 1D, non-uniformly for 2D
const B = BoundingBox(points);
const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
- const newpoints = new Array();
- for (const { X, Y } of points) {
+ const newpoints: Point[] = [];
+ points.forEach((X: any, Y: any) => {
const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width);
const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height);
newpoints[newpoints.length] = new Point(qx, qy);
- }
+ });
return newpoints;
}
function TranslateTo(points: any, pt: any) {
// translates points' centroid
const c = Centroid(points);
- const newpoints = new Array();
- for (const { X, Y } of points) {
+ const newpoints: Point[] = [];
+ points.forEach((X: any, Y: any) => {
const qx = X + pt.X - c.X;
const qy = Y + pt.Y - c.Y;
newpoints[newpoints.length] = new Point(qx, qy);
- }
+ });
return newpoints;
}
function Vectorize(points: any, useBoundedRotationInvariance: any) {
// for Protractor
- var cos = 1.0;
- var sin = 0.0;
+ let cos = 1.0;
+ let sin = 0.0;
if (useBoundedRotationInvariance) {
const iAngle = Math.atan2(points[0].Y, points[0].X);
const baseOrientation = (Math.PI / 4.0) * Math.floor((iAngle + Math.PI / 8.0) / (Math.PI / 4.0));
cos = Math.cos(baseOrientation - iAngle);
sin = Math.sin(baseOrientation - iAngle);
}
- var sum = 0.0;
- const vector = new Array<number>();
- for (var i = 0; i < points.length; i++) {
+ let sum = 0.0;
+ const vector: number[] = [];
+ for (let i = 0; i < points.length; i++) {
const newX = points[i].X * cos - points[i].Y * sin;
const newY = points[i].Y * cos + points[i].X * sin;
vector[vector.length] = newX;
@@ -481,16 +501,16 @@ function Vectorize(points: any, useBoundedRotationInvariance: any) {
sum += newX * newX + newY * newY;
}
const magnitude = Math.sqrt(sum);
- for (var i = 0; i < vector.length; i++) {
+ for (let i = 0; i < vector.length; i++) {
vector[i] /= magnitude;
}
return vector;
}
function OptimalCosineDistance(v1: any, v2: any) {
// for Protractor
- var a = 0.0;
- var b = 0.0;
- for (var i = 0; i < v1.length; i += 2) {
+ let a = 0.0;
+ let b = 0.0;
+ for (let i = 0; i < v1.length; i += 2) {
a += v1[i] * v2[i] + v1[i + 1] * v2[i + 1];
b += v1[i] * v2[i + 1] - v1[i + 1] * v2[i];
}
@@ -498,18 +518,20 @@ function OptimalCosineDistance(v1: any, v2: any) {
return Math.acos(a * Math.cos(angle) + b * Math.sin(angle));
}
function DistanceAtBestAngle(points: any, T: any, a: any, b: any, threshold: any) {
- var x1 = Phi * a + (1.0 - Phi) * b;
- var f1 = DistanceAtAngle(points, T, x1);
- var x2 = (1.0 - Phi) * a + Phi * b;
- var f2 = DistanceAtAngle(points, T, x2);
+ let x1 = Phi * a + (1.0 - Phi) * b;
+ let f1 = DistanceAtAngle(points, T, x1);
+ let x2 = (1.0 - Phi) * a + Phi * b;
+ let f2 = DistanceAtAngle(points, T, x2);
while (Math.abs(b - a) > threshold) {
if (f1 < f2) {
+ // eslint-disable-next-line no-param-reassign
b = x2;
x2 = x1;
f2 = f1;
x1 = Phi * a + (1.0 - Phi) * b;
f1 = DistanceAtAngle(points, T, x1);
} else {
+ // eslint-disable-next-line no-param-reassign
a = x1;
x1 = x2;
f1 = f2;
@@ -523,34 +545,34 @@ function DistanceAtAngle(points: any, T: any, radians: any) {
const newpoints = RotateBy(points, radians);
return PathDistance(newpoints, T.Points);
}
-function Centroid(points: any) {
- var x = 0.0,
- y = 0.0;
- for (const point of points) {
+function Centroid(points: Point[]) {
+ let x = 0.0;
+ let y = 0.0;
+ points.forEach(point => {
x += point.X;
y += point.Y;
- }
+ });
x /= points.length;
y /= points.length;
return new Point(x, y);
}
-function BoundingBox(points: any) {
- var minX = +Infinity,
- maxX = -Infinity,
- minY = +Infinity,
- maxY = -Infinity;
- for (const { X, Y } of points) {
+function BoundingBox(points: Point[]) {
+ let minX = +Infinity;
+ let maxX = -Infinity;
+ let minY = +Infinity;
+ let maxY = -Infinity;
+ points.forEach((X: any, Y: any) => {
minX = Math.min(minX, X);
minY = Math.min(minY, Y);
maxX = Math.max(maxX, X);
maxY = Math.max(maxY, Y);
- }
+ });
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
function PathDistance(pts1: any, pts2: any) {
// average distance between corresponding points in two paths
- var d = 0.0;
- for (var i = 0; i < pts1.length; i++) {
+ let d = 0.0;
+ for (let i = 0; i < pts1.length; i++) {
// assumes pts1.length == pts2.length
d += Distance(pts1[i], pts2[i]);
}
@@ -558,8 +580,8 @@ function PathDistance(pts1: any, pts2: any) {
}
function PathLength(points: any) {
// length traversed by a point path
- var d = 0.0;
- for (var i = 1; i < points.length; i++) {
+ let d = 0.0;
+ for (let i = 1; i < points.length; i++) {
d += Distance(points[i - 1], points[i]);
}
return d;
@@ -570,9 +592,9 @@ function Distance(p1: any, p2: any) {
const dy = p2.Y - p1.Y;
return Math.sqrt(dx * dx + dy * dy);
}
-function CalcStartUnitVector(points: any, index: any) {
+function CalcStartUnitVector(points: Point[], index: any) {
// start angle from points[0] to points[index] normalized as a unit vector
- const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y);
+ const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
return new Point(v.X / len, v.Y / len);
}
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 55b50cc12..520ebb42e 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -1,14 +1,14 @@
import { exec } from 'child_process';
import { Color, yellow } from 'colors';
import { createWriteStream, exists, mkdir, readFile, unlink, writeFile } from 'fs';
-import * as nodemailer from "nodemailer";
-import { MailOptions } from "nodemailer/lib/json-transport";
+import * as nodemailer from 'nodemailer';
+import { MailOptions } from 'nodemailer/lib/json-transport';
import * as path from 'path';
-import { rimraf } from "rimraf";
+import { rimraf } from 'rimraf';
import { ExecOptions } from 'shelljs';
import * as Mail from 'nodemailer/lib/mailer';
-const projectRoot = path.resolve(__dirname, "../../");
+const projectRoot = path.resolve(__dirname, '../../');
export function pathFromRoot(relative?: string) {
if (!relative) {
return projectRoot;
@@ -16,36 +16,37 @@ export function pathFromRoot(relative?: string) {
return path.resolve(projectRoot, relative);
}
-export async function fileDescriptorFromStream(path: string) {
- const logStream = createWriteStream(path);
- return new Promise<number>(resolve => logStream.on("open", resolve));
+export async function fileDescriptorFromStream(filePath: string) {
+ const logStream = createWriteStream(filePath);
+ return new Promise<number>(resolve => {
+ logStream.on('open', resolve);
+ });
}
-export const command_line = (command: string, fromDirectory?: string) => {
- return new Promise<string>((resolve, reject) => {
+export const commandLine = (command: string, fromDirectory?: string) =>
+ new Promise<string>((resolve, reject) => {
const options: ExecOptions = {};
if (fromDirectory) {
options.cwd = fromDirectory ? path.resolve(projectRoot, fromDirectory) : projectRoot;
}
- exec(command, options, (err, stdout) => err ? reject(err) : resolve(stdout));
+ exec(command, options, (err, stdout) => (err ? reject(err) : resolve(stdout)));
});
-};
-export const read_text_file = (relativePath: string) => {
+export const readTextFile = (relativePath: string) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<string>((resolve, reject) => {
- readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
+ readFile(target, (err, data) => (err ? reject(err) : resolve(data.toString())));
});
};
-export const write_text_file = (relativePath: string, contents: any) => {
+export const writeTextFile = (relativePath: string, contents: any) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<void>((resolve, reject) => {
- writeFile(target, contents, (err) => err ? reject(err) : resolve());
+ writeFile(target, contents, err => (err ? reject(err) : resolve()));
});
};
-export type Messager<T> = (outcome: { result: T | undefined, error: Error | null }) => string;
+export type Messager<T> = (outcome: { result: T | undefined; error: Error | null }) => string;
export interface LogData<T> {
startMessage: string;
@@ -55,70 +56,80 @@ export interface LogData<T> {
color?: Color;
}
+function logHelper(content: string, color: Color | string) {
+ if (typeof color === 'string') {
+ console.log(color, content);
+ } else {
+ console.log(color(content));
+ }
+}
+
let current = Math.ceil(Math.random() * 20);
-export async function log_execution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
- let result: T | undefined = undefined, error: Error | null = null;
- const resolvedColor = color || `\x1b[${31 + ++current % 6}m%s\x1b[0m`;
- log_helper(`${startMessage}...`, resolvedColor);
+export async function logExecution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
+ let result: T | undefined;
+ let error: Error | null = null;
+ const resolvedColor = color || `\x1b[${31 + (++current % 6)}m%s\x1b[0m`;
+ logHelper(`${startMessage}...`, resolvedColor);
try {
result = await action();
} catch (e: any) {
error = e;
} finally {
- log_helper(typeof endMessage === "string" ? endMessage : endMessage({ result, error }), resolvedColor);
+ logHelper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
}
return result;
}
-
-function log_helper(content: string, color: Color | string) {
- if (typeof color === "string") {
- console.log(color, content);
- } else {
- console.log(color(content));
- }
-}
-
export function logPort(listener: string, port: number) {
console.log(`${listener} listening on port ${yellow(String(port))}`);
}
export function msToTime(duration: number) {
- const milliseconds = Math.floor((duration % 1000) / 100),
- seconds = Math.floor((duration / 1000) % 60),
- minutes = Math.floor((duration / (1000 * 60)) % 60),
- hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
+ const milliseconds = Math.floor((duration % 1000) / 100);
+ const seconds = Math.floor((duration / 1000) % 60);
+ const minutes = Math.floor((duration / (1000 * 60)) % 60);
+ const hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
- const hoursS = (hours < 10) ? "0" + hours : hours;
- const minutesS = (minutes < 10) ? "0" + minutes : minutes;
- const secondsS = (seconds < 10) ? "0" + seconds : seconds;
+ const hoursS = hours < 10 ? '0' + hours : hours;
+ const minutesS = minutes < 10 ? '0' + minutes : minutes;
+ const secondsS = seconds < 10 ? '0' + seconds : seconds;
- return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds;
+ return hoursS + ':' + minutesS + ':' + secondsS + '.' + milliseconds;
}
-export const createIfNotExists = async (path: string) => {
- if (await new Promise<boolean>(resolve => exists(path, resolve))) {
+export const createIfNotExists = async (filePath: string) => {
+ if (
+ await new Promise<boolean>(resolve => {
+ exists(filePath, resolve);
+ })
+ ) {
return true;
}
- return new Promise<boolean>(resolve => mkdir(path, error => resolve(error === null)));
+ return new Promise<boolean>(resolve => {
+ mkdir(filePath, error => resolve(error === null));
+ });
};
export async function Prune(rootDirectory: string): Promise<boolean> {
// const error = await new Promise<Error>(resolve => rimraf(rootDirectory).then(resolve));
- await new Promise<void>(resolve => rimraf(rootDirectory).then(() => resolve()));
+ await new Promise<void>(resolve => {
+ rimraf(rootDirectory).then(() => resolve());
+ });
// return error === null;
return true;
}
-export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null)));
+export const Destroy = (mediaPath: string) =>
+ new Promise<boolean>(resolve => {
+ unlink(mediaPath, error => resolve(error === null));
+ });
export namespace Email {
-
const smtpTransport = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'browndashptc@gmail.com',
- pass: 'TsarNicholas#2'
- }
+ pass: 'TsarNicholas#2',
+ },
});
export interface DispatchOptions<T extends string | string[]> {
@@ -135,16 +146,18 @@ export namespace Email {
export async function dispatchAll({ to, subject, content, attachments }: DispatchOptions<string[]>) {
const failures: DispatchFailure[] = [];
- await Promise.all(to.map(async recipient => {
- let error: Error | null;
- const resolved = attachments ? "length" in attachments ? attachments : [attachments] : undefined;
- if ((error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved })) !== null) {
- failures.push({
- recipient,
- error
- });
- }
- }));
+ await Promise.all(
+ to.map(async recipient => {
+ const resolved = attachments ? ('length' in attachments ? attachments : [attachments]) : undefined;
+ const error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved });
+ if (error !== null) {
+ failures.push({
+ recipient,
+ error,
+ });
+ }
+ })
+ );
return failures.length ? failures : undefined;
}
@@ -153,10 +166,11 @@ export namespace Email {
to,
from: 'browndashptc@gmail.com',
subject,
- text: `Hello ${to.split("@")[0]},\n\n${content}`,
- attachments
+ text: `Hello ${to.split('@')[0]},\n\n${content}`,
+ attachments,
} as MailOptions;
- return new Promise<Error | null>(resolve => smtpTransport.sendMail(mailOptions, resolve));
+ return new Promise<Error | null>(resolve => {
+ smtpTransport.sendMail(mailOptions, resolve);
+ });
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index 27e9de065..f55495b2e 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,4 +1,4 @@
-import { RouteInitializer } from "../RouteManager";
+import { RouteInitializer } from '../RouteManager';
export type Registration = (initializer: RouteInitializer) => void;
@@ -8,4 +8,4 @@ export default abstract class ApiManager {
public register(register: Registration) {
this.initialize(register);
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts
index 0d43130d1..88f22992d 100644
--- a/src/server/ApiManagers/DataVizManager.ts
+++ b/src/server/ApiManagers/DataVizManager.ts
@@ -1,14 +1,14 @@
-import { csvParser, csvToString } from "../DataVizUtils";
-import { Method, _success } from "../RouteManager";
-import ApiManager, { Registration } from "./ApiManager";
-import { Directory, serverPathToFile } from "./UploadManager";
import * as path from 'path';
+import { csvParser, csvToString } from '../DataVizUtils';
+import { Method, _success } from '../RouteManager';
+import { Directory, serverPathToFile } from '../SocketData';
+import ApiManager, { Registration } from './ApiManager';
export default class DataVizManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.GET,
- subscription: "/csvData",
+ subscription: '/csvData',
secureHandler: async ({ req, res }) => {
const uri = req.query.uri as string;
@@ -19,8 +19,7 @@ export default class DataVizManager extends ApiManager {
_success(res, parsedCsv);
resolve();
});
- }
+ },
});
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index c6c4ca464..9ad334c1b 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,12 +1,12 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method, _permission_denied } from '../RouteManager';
-import { WebSocket } from '../websocket';
-import { Database } from '../database';
+import { mkdirSync } from 'fs';
import { rimraf } from 'rimraf';
-import { filesDirectory } from '..';
+import { filesDirectory } from '../SocketData';
import { DashUploadUtils } from '../DashUploadUtils';
-import { mkdirSync } from 'fs';
+import { Method } from '../RouteManager';
import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
+import { WebSocket } from '../websocket';
+import ApiManager, { Registration } from './ApiManager';
export default class DeleteManager extends ApiManager {
protected initialize(register: Registration): void {
@@ -24,9 +24,11 @@ export default class DeleteManager extends ApiManager {
switch (target) {
case 'all':
all = true;
+ // eslint-disable-next-line no-fallthrough
case 'database':
await WebSocket.doDelete(false);
if (!all) break;
+ // eslint-disable-next-line no-fallthrough
case 'files':
rimraf.sync(filesDirectory);
mkdirSync(filesDirectory);
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 2175b6db6..5ee21fb44 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -1,13 +1,13 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
import * as Archiver from 'archiver';
import * as express from 'express';
-import { Database } from "../database";
-import * as path from "path";
-import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils";
-import { publicDirectory } from "..";
-import { serverPathToFile, Directory } from "./UploadManager";
+import * as path from 'path';
+import { URL } from 'url';
+import { DashUploadUtils, SizeSuffix } from '../DashUploadUtils';
+import { Method } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { Directory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
export type Hierarchy = { [id: string]: string | Hierarchy };
export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
@@ -16,147 +16,45 @@ export interface DocumentElements {
title: string;
}
-export default class DownloadManager extends ApiManager {
-
- protected initialize(register: Registration): void {
-
- /**
- * Let's say someone's using Dash to organize images in collections.
- * This lets them export the hierarchy they've built to their
- * own file system in a useful format.
- *
- * This handler starts with a single document id (interesting only
- * if it's that of a collection). It traverses the database, captures
- * the nesting of only nested images or collections, writes
- * that to a zip file and returns it to the client for download.
- */
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("imageHierarchyExport").add('docId'),
- secureHandler: async ({ req, res }) => {
- const id = req.params.docId;
- const hierarchy: Hierarchy = {};
- await buildHierarchyRecursive(id, hierarchy);
- return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
- }
- });
-
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("downloadId").add("docId"),
- secureHandler: async ({ req, res }) => {
- return BuildAndDispatchZip(res, async zip => {
- const { id, docs, files } = await getDocs(req.params.docId);
- const docString = JSON.stringify({ id, docs });
- zip.append(docString, { name: "doc.json" });
- files.forEach(val => {
- zip.file(publicDirectory + val, { name: val.substring(1) });
- });
- });
- }
- });
-
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("serializeDoc").add("docId"),
- secureHandler: async ({ req, res }) => {
- const { docs, files } = await getDocs(req.params.docId);
- res.send({ docs, files: Array.from(files) });
- }
- });
-
- }
-
-}
-
-async function getDocs(id: string) {
- const files = new Set<string>();
- const docs: { [id: string]: any } = {};
- const fn = (doc: any): string[] => {
- const id = doc.id;
- if (typeof id === "string" && id.endsWith("Proto")) {
- //Skip protos
- return [];
- }
- const ids: string[] = [];
- for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) { continue; }
- const field = doc.fields[key];
- if (field === undefined || field === null) { continue; }
-
- if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
- ids.push(field.fieldId);
- } else if (field.__type === "script" || field.__type === "computed") {
- field.captures && ids.push(field.captures.fieldId);
- } else if (field.__type === "list") {
- ids.push(...fn(field));
- } else if (typeof field === "string") {
- const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
- let match: string[] | null;
- while ((match = re.exec(field)) !== null) {
- ids.push(match[1]);
- }
- } else if (field.__type === "RichTextField") {
- const re = /"href"\s*:\s*"(.*?)"/g;
- let match: string[] | null;
- while ((match = re.exec(field.Data)) !== null) {
- const urlString = match[1];
- const split = new URL(urlString).pathname.split("doc/");
- if (split.length > 1) {
- ids.push(split[split.length - 1]);
- }
- }
- const re2 = /"src"\s*:\s*"(.*?)"/g;
- while ((match = re2.exec(field.Data)) !== null) {
- const urlString = match[1];
- const pathname = new URL(urlString).pathname;
- files.add(pathname);
- }
- } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
- const url = new URL(field.url);
- const pathname = url.pathname;
- files.add(pathname);
- }
- }
-
- if (doc.id) {
- docs[doc.id] = doc;
- }
- return ids;
- };
- await Database.Instance.visit([id], fn);
- return { id, docs, files };
-}
-
/**
- * This utility function factors out the process
- * of creating a zip file and sending it back to the client
- * by piping it into a response.
- *
- * Learn more about piping and readable / writable streams here!
- * https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
- *
- * @param res the writable stream response object that will transfer the generated zip file
- * @param mutator the callback function used to actually modify and insert information into the zip instance
+ * This is a very specific utility method to help traverse the database
+ * to parse data and titles out of images and collections alone.
+ *
+ * We don't know if the document id given to is corresponds to a view document or a data
+ * document. If it's a data document, the response from the database will have
+ * a data field. If not, call recursively on the proto, and resolve with *its* data
+ *
+ * @param targetId the id of the Dash document whose data is being requests
+ * @returns the data of the document, as well as its title
*/
-export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
- res.set('Content-disposition', `attachment;`);
- res.set('Content-Type', "application/zip");
- const zip = Archiver('zip');
- zip.pipe(res);
- await mutator(zip);
- return zip.finalize();
+async function getData(targetId: string): Promise<DocumentElements> {
+ return new Promise<DocumentElements>((resolve, reject) => {
+ Database.Instance.getDocument(targetId, async (result: any) => {
+ const { data, proto, title } = result.fields;
+ if (data) {
+ if (data.url) {
+ resolve({ data: data.url, title });
+ } else if (data.fields) {
+ resolve({ data: data.fields, title });
+ } else {
+ reject();
+ }
+ } else if (proto) {
+ getData(proto.fieldId).then(resolve, reject);
+ } else {
+ reject();
+ }
+ });
+ });
}
/**
* This function starts with a single document id as a seed,
* typically that of a collection, and then descends the entire tree
- * of image or collection documents that are reachable from that seed.
+ * of image or collection documents that are reachable from that seed.
* @param seedId the id of the root of the subtree we're trying to capture, interesting only if it's a collection
* @param hierarchy the data structure we're going to use to record the nesting of the collections and images as we descend
- */
-
-/*
+
Below is an example of the JSON hierarchy built from two images contained inside a collection titled 'a nested collection',
following the general recursive structure shown immediately below
{
@@ -190,74 +88,175 @@ async function buildHierarchyRecursive(seedId: string, hierarchy: Hierarchy): Pr
}
/**
- * This is a very specific utility method to help traverse the database
- * to parse data and titles out of images and collections alone.
- *
- * We don't know if the document id given to is corresponds to a view document or a data
- * document. If it's a data document, the response from the database will have
- * a data field. If not, call recursively on the proto, and resolve with *its* data
- *
- * @param targetId the id of the Dash document whose data is being requests
- * @returns the data of the document, as well as its title
+ * This utility function factors out the process
+ * of creating a zip file and sending it back to the client
+ * by piping it into a response.
+ *
+ * Learn more about piping and readable / writable streams here!
+ * https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
+ *
+ * @param res the writable stream response object that will transfer the generated zip file
+ * @param mutator the callback function used to actually modify and insert information into the zip instance
*/
-async function getData(targetId: string): Promise<DocumentElements> {
- return new Promise<DocumentElements>((resolve, reject) => {
- Database.Instance.getDocument(targetId, async (result: any) => {
- const { data, proto, title } = result.fields;
- if (data) {
- if (data.url) {
- resolve({ data: data.url, title });
- } else if (data.fields) {
- resolve({ data: data.fields, title });
- } else {
- reject();
- }
- } else if (proto) {
- getData(proto.fieldId).then(resolve, reject);
- } else {
- reject();
- }
- });
- });
+export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
+ res.set('Content-disposition', `attachment;`);
+ res.set('Content-Type', 'application/zip');
+ const zip = Archiver('zip');
+ zip.pipe(res);
+ await mutator(zip);
+ return zip.finalize();
}
/**
- *
+ *
* @param file the zip file to which we write the files
* @param hierarchy the data structure from which we read, defining the nesting of the documents in the zip
* @param prefix lets us create nested folders in the zip file by continually appending to the end
* of the prefix with each layer of recursion.
- *
+ *
* Function Call #1 => "Dash Export"
* Function Call #2 => "Dash Export/a nested collection"
* Function Call #3 => "Dash Export/a nested collection/lowest level collection"
* ...
*/
-async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = "Dash Export"): Promise<void> {
- for (const documentTitle of Object.keys(hierarchy)) {
- const result = hierarchy[documentTitle];
- // base case or leaf node, we've hit a url (image)
- if (typeof result === "string") {
- let path: string;
- let matches: RegExpExecArray | null;
- if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
- // image already exists on our server
- path = serverPathToFile(Directory.images, matches[1]);
+async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = 'Dash Export'): Promise<void> {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const documentTitle in hierarchy) {
+ if (Object.prototype.hasOwnProperty.call(hierarchy, documentTitle)) {
+ const result = hierarchy[documentTitle];
+ // base case or leaf node, we've hit a url (image)
+ if (typeof result === 'string') {
+ let fPath: string;
+ const matches = /:\d+\/files\/images\/(upload_[\da-z]{32}.*)/g.exec(result);
+ if (matches !== null) {
+ // image already exists on our server
+ fPath = serverPathToFile(Directory.images, matches[1]);
+ } else {
+ // the image doesn't already exist on our server (may have been dragged
+ // and dropped in the browser and thus hosted remotely) so we upload it
+ // to our server and point the zip file to it, so it can bundle up the bytes
+ // eslint-disable-next-line no-await-in-loop
+ const information = await DashUploadUtils.UploadImage(result);
+ fPath = information instanceof Error ? '' : information.accessPaths[SizeSuffix.Original].server;
+ }
+ // write the file specified by the path to the directory in the
+ // zip file given by the prefix.
+ if (fPath) {
+ file.file(fPath, { name: documentTitle, prefix });
+ }
} else {
- // the image doesn't already exist on our server (may have been dragged
- // and dropped in the browser and thus hosted remotely) so we upload it
- // to our server and point the zip file to it, so it can bundle up the bytes
- const information = await DashUploadUtils.UploadImage(result);
- path = information instanceof Error ? "" : information.accessPaths[SizeSuffix.Original].server;
+ // we've hit a collection, so we have to recurse
+ // eslint-disable-next-line no-await-in-loop
+ await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
}
- // write the file specified by the path to the directory in the
- // zip file given by the prefix.
- if (path) {
- file.file(path, { name: documentTitle, prefix });
+ }
+ }
+}
+
+async function getDocs(docId: string) {
+ const files = new Set<string>();
+ const docs: { [id: string]: any } = {};
+ const fn = (doc: any): string[] => {
+ const { id } = doc;
+ if (typeof id === 'string' && id.endsWith('Proto')) {
+ // Skip protos
+ return [];
+ }
+ const ids: string[] = [];
+ // eslint-disable-next-line no-restricted-syntax
+ for (const key in doc.fields) {
+ // eslint-disable-next-line no-continue
+ if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
+ const field = doc.fields[key];
+ // eslint-disable-next-line no-continue
+ if (field === undefined || field === null) continue;
+
+ if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
+ ids.push(field.fieldId);
+ } else if (field.__type === 'script' || field.__type === 'computed') {
+ field.captures && ids.push(field.captures.fieldId);
+ } else if (field.__type === 'list') {
+ ids.push(...fn(field));
+ } else if (typeof field === 'string') {
+ const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w-]*)"/g;
+ for (let match = re.exec(field); match !== null; match = re.exec(field)) {
+ ids.push(match[1]);
+ }
+ } else if (field.__type === 'RichTextField') {
+ const re = /"href"\s*:\s*"(.*?)"/g;
+ for (let match = re.exec(field.data); match !== null; match = re.exec(field.Data)) {
+ const urlString = match[1];
+ const split = new URL(urlString).pathname.split('doc/');
+ if (split.length > 1) {
+ ids.push(split[split.length - 1]);
+ }
+ }
+ const re2 = /"src"\s*:\s*"(.*?)"/g;
+ for (let match = re2.exec(field.Data); match !== null; match = re2.exec(field.Data)) {
+ const urlString = match[1];
+ const { pathname } = new URL(urlString);
+ files.add(pathname);
+ }
+ } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
+ const { pathname } = new URL(field.url);
+ files.add(pathname);
}
- } else {
- // we've hit a collection, so we have to recurse
- await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
}
+
+ if (doc.id) {
+ docs[doc.id] = doc;
+ }
+ return ids;
+ };
+ await Database.Instance.visit([docId], fn);
+ return { id: docId, docs, files };
+}
+
+export default class DownloadManager extends ApiManager {
+ protected initialize(register: Registration): void {
+ /**
+ * Let's say someone's using Dash to organize images in collections.
+ * This lets them export the hierarchy they've built to their
+ * own file system in a useful format.
+ *
+ * This handler starts with a single document id (interesting only
+ * if it's that of a collection). It traverses the database, captures
+ * the nesting of only nested images or collections, writes
+ * that to a zip file and returns it to the client for download.
+ */
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('imageHierarchyExport').add('docId'),
+ secureHandler: async ({ req, res }) => {
+ const id = req.params.docId;
+ const hierarchy: Hierarchy = {};
+ await buildHierarchyRecursive(id, hierarchy);
+ return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
+ },
+ });
+
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('downloadId').add('docId'),
+ secureHandler: async ({ req, res }) =>
+ BuildAndDispatchZip(res, async zip => {
+ const { id, docs, files } = await getDocs(req.params.docId);
+ const docString = JSON.stringify({ id, docs });
+ zip.append(docString, { name: 'doc.json' });
+ files.forEach(val => {
+ zip.file(publicDirectory + val, { name: val.substring(1) });
+ });
+ }),
+ });
+
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('serializeDoc').add('docId'),
+ secureHandler: async ({ req, res }) => {
+ const { docs, files } = await getDocs(req.params.docId);
+ res.send({ docs, files: Array.from(files) });
+ },
+ });
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index f94b77cac..12913b1ef 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,51 +1,49 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import RouteSubscriber from "../RouteSubscriber";
-import { Database } from "../database";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
+import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
- ["create", (api, params) => api.create(params)],
- ["retrieve", (api, params) => api.get(params)],
- ["update", (api, params) => api.batchUpdate(params)],
+ ['create', (api, params) => api.create(params)],
+ ['retrieve', (api, params) => api.get(params)],
+ ['update', (api, params) => api.batchUpdate(params)],
]);
export default class GeneralGoogleManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: "/readGoogleAccessToken",
+ subscription: '/readGoogleAccessToken',
secureHandler: async ({ user, res }) => {
- const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id));
+ const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
if (!credentials?.access_token) {
return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
}
return res.send(credentials);
- }
+ },
});
register({
method: Method.POST,
- subscription: "/writeGoogleAccessToken",
+ subscription: '/writeGoogleAccessToken',
secureHandler: async ({ user, req, res }) => {
res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode));
- }
+ },
});
register({
method: Method.GET,
- subscription: "/revokeGoogleAccessToken",
+ subscription: '/revokeGoogleAccessToken',
secureHandler: async ({ user, res }) => {
await Database.Auxiliary.GoogleAccessToken.Revoke(user.id);
res.send();
- }
+ },
});
register({
method: Method.POST,
- subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
+ subscription: new RouteSubscriber('googleDocs').add('sector', 'action'),
secureHandler: async ({ req, res, user }) => {
const sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
const action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
@@ -61,8 +59,7 @@ export default class GeneralGoogleManager extends ApiManager {
return;
}
res.send(undefined);
- }
+ },
});
-
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/MongoStore.js b/src/server/ApiManagers/MongoStore.js
new file mode 100644
index 000000000..5d91c2805
--- /dev/null
+++ b/src/server/ApiManagers/MongoStore.js
@@ -0,0 +1,413 @@
+const __createBinding =
+ (this && this.__createBinding) ||
+ (Object.create
+ ? function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ let desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = {
+ enumerable: true,
+ get: function () {
+ return m[k];
+ },
+ };
+ }
+ Object.defineProperty(o, k2, desc);
+ }
+ : function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+ });
+const __setModuleDefault =
+ (this && this.__setModuleDefault) ||
+ (Object.create
+ ? function (o, v) {
+ Object.defineProperty(o, 'default', { enumerable: true, value: v });
+ }
+ : function (o, v) {
+ o.default = v;
+ });
+const __importStar =
+ (this && this.__importStar) ||
+ function (mod) {
+ if (mod && mod.__esModule) return mod;
+ const result = {};
+ if (mod != null) for (const k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+ };
+const __importDefault =
+ (this && this.__importDefault) ||
+ function (mod) {
+ return mod && mod.__esModule ? mod : { default: mod };
+ };
+Object.defineProperty(exports, '__esModule', { value: true });
+const console_1 = require('console');
+const util_1 = __importDefault(require('util'));
+const session = __importStar(require('express-session'));
+const mongodb_1 = require('mongodb');
+const debug_1 = __importDefault(require('debug'));
+const debug = (0, debug_1.default)('connect-mongo');
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const noop = () => {};
+const unit = a => a;
+function defaultSerializeFunction(session) {
+ // Copy each property of the session to a new object
+ const obj = {};
+ let prop;
+ for (prop in session) {
+ if (prop === 'cookie') {
+ // Convert the cookie instance to an object, if possible
+ // This gets rid of the duplicate object under session.cookie.data property
+ // @ts-ignore FIXME:
+ obj.cookie = session.cookie.toJSON
+ ? // @ts-ignore FIXME:
+ session.cookie.toJSON()
+ : session.cookie;
+ } else {
+ // @ts-ignore FIXME:
+ obj[prop] = session[prop];
+ }
+ }
+ return obj;
+}
+function computeTransformFunctions(options) {
+ if (options.serialize || options.unserialize) {
+ return {
+ serialize: options.serialize || defaultSerializeFunction,
+ unserialize: options.unserialize || unit,
+ };
+ }
+ if (options.stringify === false) {
+ return {
+ serialize: defaultSerializeFunction,
+ unserialize: unit,
+ };
+ }
+ // Default case
+ return {
+ serialize: JSON.stringify,
+ unserialize: JSON.parse,
+ };
+}
+class MongoStore extends session.Store {
+ constructor({ collectionName = 'sessions', ttl = 1209600, mongoOptions = {}, autoRemove = 'native', autoRemoveInterval = 10, touchAfter = 0, stringify = true, crypto, ...required }) {
+ super();
+ this.crypto = null;
+ debug('create MongoStore instance');
+ const options = {
+ collectionName,
+ ttl,
+ mongoOptions,
+ autoRemove,
+ autoRemoveInterval,
+ touchAfter,
+ stringify,
+ crypto: {
+ ...{
+ secret: false,
+ algorithm: 'aes-256-gcm',
+ hashing: 'sha512',
+ encodeas: 'base64',
+ key_size: 32,
+ iv_size: 16,
+ at_size: 16,
+ },
+ ...crypto,
+ },
+ ...required,
+ };
+ // Check params
+ (0, console_1.assert)(options.mongoUrl || options.clientPromise || options.client, 'You must provide either mongoUrl|clientPromise|client in options');
+ (0, console_1.assert)(options.createAutoRemoveIdx === null || options.createAutoRemoveIdx === undefined, 'options.createAutoRemoveIdx has been reverted to autoRemove and autoRemoveInterval');
+ (0, console_1.assert)(!options.autoRemoveInterval || options.autoRemoveInterval <= 71582, /* (Math.pow(2, 32) - 1) / (1000 * 60) */ 'autoRemoveInterval is too large. options.autoRemoveInterval is in minutes but not seconds nor mills');
+ this.transformFunctions = computeTransformFunctions(options);
+ let _clientP;
+ if (options.mongoUrl) {
+ _clientP = mongodb_1.MongoClient.connect(options.mongoUrl, options.mongoOptions);
+ } else if (options.clientPromise) {
+ _clientP = options.clientPromise;
+ } else if (options.client) {
+ _clientP = Promise.resolve(options.client);
+ } else {
+ throw new Error('Cannot init client. Please provide correct options');
+ }
+ (0, console_1.assert)(!!_clientP, 'Client is null|undefined');
+ this.clientP = _clientP;
+ this.options = options;
+ this.collectionP = _clientP.then(async con => {
+ const collection = con.db(options.dbName).collection(options.collectionName);
+ await this.setAutoRemove(collection);
+ return collection;
+ });
+ if (options.crypto.secret) {
+ this.crypto = require('kruptein')(options.crypto);
+ }
+ }
+ static create(options) {
+ return new MongoStore(options);
+ }
+ setAutoRemove(collection) {
+ const removeQuery = () => ({
+ expires: {
+ $lt: new Date(),
+ },
+ });
+ switch (this.options.autoRemove) {
+ case 'native':
+ debug('Creating MongoDB TTL index');
+ return collection.createIndex(
+ { expires: 1 },
+ {
+ background: true,
+ expireAfterSeconds: 0,
+ }
+ );
+ case 'interval':
+ debug('create Timer to remove expired sessions');
+ this.timer = setInterval(
+ () =>
+ collection.deleteMany(removeQuery(), {
+ writeConcern: {
+ w: 0,
+ j: false,
+ },
+ }),
+ this.options.autoRemoveInterval * 1000 * 60
+ );
+ this.timer.unref();
+ return Promise.resolve();
+ case 'disabled':
+ default:
+ return Promise.resolve();
+ }
+ }
+ computeStorageId(sessionId) {
+ if (this.options.transformId && typeof this.options.transformId === 'function') {
+ return this.options.transformId(sessionId);
+ }
+ return sessionId;
+ }
+ /**
+ * promisify and bind the `this.crypto.get` function.
+ * Please check !!this.crypto === true before using this getter!
+ */
+ get cryptoGet() {
+ if (!this.crypto) {
+ throw new Error('Check this.crypto before calling this.cryptoGet!');
+ }
+ return util_1.default.promisify(this.crypto.get).bind(this.crypto);
+ }
+ /**
+ * Decrypt given session data
+ * @param session session data to be decrypt. Mutate the input session.
+ */
+ async decryptSession(session) {
+ if (this.crypto && session) {
+ const plaintext = await this.cryptoGet(this.options.crypto.secret, session.session).catch(err => {
+ throw new Error(err);
+ });
+ // @ts-ignore
+ session.session = JSON.parse(plaintext);
+ }
+ }
+ /**
+ * Get a session from the store given a session ID (sid)
+ * @param sid session ID
+ */
+ get(sid, callback) {
+ (async () => {
+ try {
+ debug(`MongoStore#get=${sid}`);
+ const collection = await this.collectionP;
+ const session = await collection.findOne({
+ _id: this.computeStorageId(sid),
+ $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+ });
+ if (this.crypto && session) {
+ await this.decryptSession(session).catch(err => callback(err));
+ }
+ const s = session && this.transformFunctions.unserialize(session.session);
+ if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+ s.lastModified = session.lastModified;
+ }
+ this.emit('get', sid);
+ callback(null, s === undefined ? null : s);
+ } catch (error) {
+ callback(error);
+ }
+ })();
+ }
+ /**
+ * Upsert a session into the store given a session ID (sid) and session (session) object.
+ * @param sid session ID
+ * @param session session object
+ */
+ set(sid, session, callback = noop) {
+ (async () => {
+ let _a;
+ try {
+ debug(`MongoStore#set=${sid}`);
+ // Removing the lastModified prop from the session object before update
+ // @ts-ignore
+ if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+ // @ts-ignore
+ delete session.lastModified;
+ }
+ const s = {
+ _id: this.computeStorageId(sid),
+ session: this.transformFunctions.serialize(session),
+ };
+ // Expire handling
+ if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+ s.expires = new Date(session.cookie.expires);
+ } else {
+ // If there's no expiration date specified, it is
+ // browser-session cookie or there is no cookie at all,
+ // as per the connect docs.
+ //
+ // So we set the expiration to two-weeks from now
+ // - as is common practice in the industry (e.g Django) -
+ // or the default specified in the options.
+ s.expires = new Date(Date.now() + this.options.ttl * 1000);
+ }
+ // Last modify handling
+ if (this.options.touchAfter > 0) {
+ s.lastModified = new Date();
+ }
+ if (this.crypto) {
+ const cryptoSet = util_1.default.promisify(this.crypto.set).bind(this.crypto);
+ const data = await cryptoSet(this.options.crypto.secret, s.session).catch(err => {
+ throw new Error(err);
+ });
+ s.session = data;
+ }
+ const collection = await this.collectionP;
+ const rawResp = await collection.updateOne(
+ { _id: s._id },
+ { $set: s },
+ {
+ upsert: true,
+ writeConcern: this.options.writeOperationOptions,
+ }
+ );
+ if (rawResp.upsertedCount > 0) {
+ this.emit('create', sid);
+ } else {
+ this.emit('update', sid);
+ }
+ this.emit('set', sid);
+ } catch (error) {
+ return callback(error);
+ }
+ return callback(null);
+ })();
+ }
+ touch(sid, session, callback = noop) {
+ (async () => {
+ let _a;
+ try {
+ debug(`MongoStore#touch=${sid}`);
+ const updateFields = {};
+ const touchAfter = this.options.touchAfter * 1000;
+ const lastModified = session.lastModified ? session.lastModified.getTime() : 0;
+ const currentDate = new Date();
+ // If the given options has a touchAfter property, check if the
+ // current timestamp - lastModified timestamp is bigger than
+ // the specified, if it's not, don't touch the session
+ if (touchAfter > 0 && lastModified > 0) {
+ const timeElapsed = currentDate.getTime() - lastModified;
+ if (timeElapsed < touchAfter) {
+ debug(`Skip touching session=${sid}`);
+ return callback(null);
+ }
+ updateFields.lastModified = currentDate;
+ }
+ if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+ updateFields.expires = new Date(session.cookie.expires);
+ } else {
+ updateFields.expires = new Date(Date.now() + this.options.ttl * 1000);
+ }
+ const collection = await this.collectionP;
+ const rawResp = await collection.updateOne({ _id: this.computeStorageId(sid) }, { $set: updateFields }, { writeConcern: this.options.writeOperationOptions });
+ if (rawResp.matchedCount === 0) {
+ return callback(new Error('Unable to find the session to touch'));
+ } else {
+ this.emit('touch', sid, session);
+ return callback(null);
+ }
+ } catch (error) {
+ return callback(error);
+ }
+ })();
+ }
+ /**
+ * Get all sessions in the store as an array
+ */
+ all(callback) {
+ (async () => {
+ try {
+ debug('MongoStore#all()');
+ const collection = await this.collectionP;
+ const sessions = collection.find({
+ $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+ });
+ const results = [];
+ for await (const session of sessions) {
+ if (this.crypto && session) {
+ await this.decryptSession(session);
+ }
+ results.push(this.transformFunctions.unserialize(session.session));
+ }
+ this.emit('all', results);
+ callback(null, results);
+ } catch (error) {
+ callback(error);
+ }
+ })();
+ }
+ /**
+ * Destroy/delete a session from the store given a session ID (sid)
+ * @param sid session ID
+ */
+ destroy(sid, callback = noop) {
+ debug(`MongoStore#destroy=${sid}`);
+ this.collectionP
+ .then(colleciton => colleciton.deleteOne({ _id: this.computeStorageId(sid) }, { writeConcern: this.options.writeOperationOptions }))
+ .then(() => {
+ this.emit('destroy', sid);
+ callback(null);
+ })
+ .catch(err => callback(err));
+ }
+ /**
+ * Get the count of all sessions in the store
+ */
+ length(callback) {
+ debug('MongoStore#length()');
+ this.collectionP
+ .then(collection => collection.countDocuments())
+ .then(c => callback(null, c))
+ // @ts-ignore
+ .catch(err => callback(err));
+ }
+ /**
+ * Delete all sessions from the store.
+ */
+ clear(callback = noop) {
+ debug('MongoStore#clear()');
+ this.collectionP
+ .then(collection => collection.drop())
+ .then(() => callback(null))
+ .catch(err => callback(err));
+ }
+ /**
+ * Close database connection
+ */
+ close() {
+ debug('MongoStore#close()');
+ return this.clientP.then(c => c.close());
+ }
+}
+exports.default = MongoStore;
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9uZ29TdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvTW9uZ29TdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEscUNBQWdDO0FBQ2hDLGdEQUF1QjtBQUN2Qix5REFBMEM7QUFDMUMscUNBS2dCO0FBQ2hCLGtEQUF5QjtBQUd6QixNQUFNLEtBQUssR0FBRyxJQUFBLGVBQUssRUFBQyxlQUFlLENBQUMsQ0FBQTtBQWdFcEMsZ0VBQWdFO0FBQ2hFLE1BQU0sSUFBSSxHQUFHLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQTtBQUNyQixNQUFNLElBQUksR0FBbUIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUVyQyxTQUFTLHdCQUF3QixDQUMvQixPQUE0QjtJQUU1QixvREFBb0Q7SUFDcEQsTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFBO0lBQ2QsSUFBSSxJQUFJLENBQUE7SUFDUixLQUFLLElBQUksSUFBSSxPQUFPLEVBQUU7UUFDcEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQ3JCLHdEQUF3RDtZQUN4RCwyRUFBMkU7WUFDM0Usb0JBQW9CO1lBQ3BCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNO2dCQUNoQyxDQUFDLENBQUMsb0JBQW9CO29CQUNwQixPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRTtnQkFDekIsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUE7U0FDbkI7YUFBTTtZQUNMLG9CQUFvQjtZQUNwQixHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO1NBQzFCO0tBQ0Y7SUFFRCxPQUFPLEdBQTBCLENBQUE7QUFDbkMsQ0FBQztBQUVELFNBQVMseUJBQXlCLENBQUMsT0FBbUM7SUFDcEUsSUFBSSxPQUFPLENBQUMsU0FBUyxJQUFJLE9BQU8sQ0FBQyxXQUFXLEVBQUU7UUFDNUMsT0FBTztZQUNMLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUyxJQUFJLHdCQUF3QjtZQUN4RCxXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVcsSUFBSSxJQUFJO1NBQ3pDLENBQUE7S0FDRjtJQUVELElBQUksT0FBTyxDQUFDLFNBQVMsS0FBSyxLQUFLLEVBQUU7UUFDL0IsT0FBTztZQUNMLFNBQVMsRUFBRSx3QkFBd0I7WUFDbkMsV0FBVyxFQUFFLElBQUk7U0FDbEIsQ0FBQTtLQUNGO0lBQ0QsZUFBZTtJQUNmLE9BQU87UUFDTCxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVM7UUFDekIsV0FBVyxFQUFFLElBQUksQ0FBQyxLQUFLO0tBQ3hCLENBQUE7QUFDSCxDQUFDO0FBRUQsTUFBcUIsVUFBVyxTQUFRLE9BQU8sQ0FBQyxLQUFLO0lBWW5ELFlBQVksRUFDVixjQUFjLEdBQUcsVUFBVSxFQUMzQixHQUFHLEdBQUcsT0FBTyxFQUNiLFlBQVksR0FBRyxFQUFFLEVBQ2pCLFVBQVUsR0FBRyxRQUFRLEVBQ3JCLGtCQUFrQixHQUFHLEVBQUUsRUFDdkIsVUFBVSxHQUFHLENBQUMsRUFDZCxTQUFTLEdBQUcsSUFBSSxFQUNoQixNQUFNLEVBQ04sR0FBRyxRQUFRLEVBQ1M7UUFDcEIsS0FBSyxFQUFFLENBQUE7UUFyQkQsV0FBTSxHQUFvQixJQUFJLENBQUE7UUFzQnBDLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO1FBQ25DLE1BQU0sT0FBTyxHQUErQjtZQUMxQyxjQUFjO1lBQ2QsR0FBRztZQUNILFlBQVk7WUFDWixVQUFVO1lBQ1Ysa0JBQWtCO1lBQ2xCLFVBQVU7WUFDVixTQUFTO1lBQ1QsTUFBTSxFQUFFO2dCQUNOLEdBQUc7b0JBQ0QsTUFBTSxFQUFFLEtBQUs7b0JBQ2IsU0FBUyxFQUFFLGFBQWE7b0JBQ3hCLE9BQU8sRUFBRSxRQUFRO29CQUNqQixRQUFRLEVBQUUsUUFBUTtvQkFDbEIsUUFBUSxFQUFFLEVBQUU7b0JBQ1osT0FBTyxFQUFFLEVBQUU7b0JBQ1gsT0FBTyxFQUFFLEVBQUU7aUJBQ1o7Z0JBQ0QsR0FBRyxNQUFNO2FBQ1Y7WUFDRCxHQUFHLFFBQVE7U0FDWixDQUFBO1FBQ0QsZUFBZTtRQUNmLElBQUEsZ0JBQU0sRUFDSixPQUFPLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQyxhQUFhLElBQUksT0FBTyxDQUFDLE1BQU0sRUFDM0Qsa0VBQWtFLENBQ25FLENBQUE7UUFDRCxJQUFBLGdCQUFNLEVBQ0osT0FBTyxDQUFDLG1CQUFtQixLQUFLLElBQUk7WUFDbEMsT0FBTyxDQUFDLG1CQUFtQixLQUFLLFNBQVMsRUFDM0Msb0ZBQW9GLENBQ3JGLENBQUE7UUFDRCxJQUFBLGdCQUFNLEVBQ0osQ0FBQyxPQUFPLENBQUMsa0JBQWtCLElBQUksT0FBTyxDQUFDLGtCQUFrQixJQUFJLEtBQUs7UUFDbEUseUNBQXlDLENBQUMscUdBQXFHLENBQ2hKLENBQUE7UUFDRCxJQUFJLENBQUMsa0JBQWtCLEdBQUcseUJBQXlCLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDNUQsSUFBSSxRQUE4QixDQUFBO1FBQ2xDLElBQUksT0FBTyxDQUFDLFFBQVEsRUFBRTtZQUNwQixRQUFRLEdBQUcscUJBQVcsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7U0FDdkU7YUFBTSxJQUFJLE9BQU8sQ0FBQyxhQUFhLEVBQUU7WUFDaEMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxhQUFhLENBQUE7U0FDakM7YUFBTSxJQUFJLE9BQU8sQ0FBQyxNQUFNLEVBQUU7WUFDekIsUUFBUSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1NBQzNDO2FBQU07WUFDTCxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxDQUFDLENBQUE7U0FDdEU7UUFDRCxJQUFBLGdCQUFNLEVBQUMsQ0FBQyxDQUFDLFFBQVEsRUFBRSwwQkFBMEIsQ0FBQyxDQUFBO1FBQzlDLElBQUksQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFBO1FBQ3ZCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxXQUFXLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUU7WUFDN0MsTUFBTSxVQUFVLEdBQUcsR0FBRztpQkFDbkIsRUFBRSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7aUJBQ2xCLFVBQVUsQ0FBc0IsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFBO1lBQzFELE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUNwQyxPQUFPLFVBQVUsQ0FBQTtRQUNuQixDQUFDLENBQUMsQ0FBQTtRQUNGLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7WUFDekIsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1NBQ2xEO0lBQ0gsQ0FBQztJQUVELE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBNEI7UUFDeEMsT0FBTyxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUNoQyxDQUFDO0lBRU8sYUFBYSxDQUNuQixVQUEyQztRQUUzQyxNQUFNLFdBQVcsR0FBRyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQ3pCLE9BQU8sRUFBRTtnQkFDUCxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUU7YUFDaEI7U0FDRixDQUFDLENBQUE7UUFDRixRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFO1lBQy9CLEtBQUssUUFBUTtnQkFDWCxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQTtnQkFDbkMsT0FBTyxVQUFVLENBQUMsV0FBVyxDQUMzQixFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsRUFDZDtvQkFDRSxVQUFVLEVBQUUsSUFBSTtvQkFDaEIsa0JBQWtCLEVBQUUsQ0FBQztpQkFDdEIsQ0FDRixDQUFBO1lBQ0gsS0FBSyxVQUFVO2dCQUNiLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFBO2dCQUNoRCxJQUFJLENBQUMsS0FBSyxHQUFHLFdBQVcsQ0FDdEIsR0FBRyxFQUFFLENBQ0gsVUFBVSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVBQUUsRUFBRTtvQkFDbkMsWUFBWSxFQUFFO3dCQUNaLENBQUMsRUFBRSxDQUFDO3dCQUNKLENBQUMsRUFBRSxLQUFLO3FCQUNUO2lCQUNGLENBQUMsRUFDSixJQUFJLENBQUMsT0FBTyxDQUFDLGtCQUFrQixHQUFHLElBQUksR0FBRyxFQUFFLENBQzVDLENBQUE7Z0JBQ0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQTtnQkFDbEIsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUE7WUFDMUIsS0FBSyxVQUFVLENBQUM7WUFDaEI7Z0JBQ0UsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUE7U0FDM0I7SUFDSCxDQUFDO0lBRU8sZ0JBQWdCLENBQUMsU0FBaUI7UUFDeEMsSUFDRSxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVc7WUFDeEIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsS0FBSyxVQUFVLEVBQzlDO1lBQ0EsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtTQUMzQztRQUNELE9BQU8sU0FBUyxDQUFBO0lBQ2xCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxJQUFZLFNBQVM7UUFDbkIsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDaEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrREFBa0QsQ0FBQyxDQUFBO1NBQ3BFO1FBQ0QsT0FBTyxjQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ssS0FBSyxDQUFDLGNBQWMsQ0FDMUIsT0FBK0M7UUFFL0MsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLE9BQU8sRUFBRTtZQUMxQixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQ3BDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQWdCLEVBQ3BDLE9BQU8sQ0FBQyxPQUFPLENBQ2hCLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUN0QixDQUFDLENBQUMsQ0FBQTtZQUNGLGFBQWE7WUFDYixPQUFPLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7U0FDeEM7SUFDSCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsR0FBRyxDQUNELEdBQVcsRUFDWCxRQUFrRTtRQUVsRSxDQUFDO1FBQUEsQ0FBQyxLQUFLLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsS0FBSyxDQUFDLGtCQUFrQixHQUFHLEVBQUUsQ0FBQyxDQUFBO2dCQUM5QixNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUE7Z0JBQ3pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQztvQkFDdkMsR0FBRyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUM7b0JBQy9CLEdBQUcsRUFBRTt3QkFDSCxFQUFFLE9BQU8sRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRTt3QkFDL0IsRUFBRSxPQUFPLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUUsRUFBRSxFQUFFO3FCQUNqQztpQkFDRixDQUFDLENBQUE7Z0JBQ0YsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLE9BQU8sRUFBRTtvQkFDMUIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUN2QixPQUF5QyxDQUMxQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7aUJBQ2hDO2dCQUNELE1BQU0sQ0FBQyxHQUNMLE9BQU8sSUFBSSxJQUFJLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQTtnQkFDakUsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsR0FBRyxDQUFDLEtBQUksT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFlBQVksQ0FBQSxFQUFFO29CQUN4RCxDQUFDLENBQUMsWUFBWSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUE7aUJBQ3RDO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFBO2dCQUNyQixRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7YUFDM0M7WUFBQyxPQUFPLEtBQUssRUFBRTtnQkFDZCxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUE7YUFDaEI7UUFDSCxDQUFDLENBQUMsRUFBRSxDQUFBO0lBQ04sQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxHQUFHLENBQ0QsR0FBVyxFQUNYLE9BQTRCLEVBQzVCLFdBQStCLElBQUk7UUFFbkMsQ0FBQztRQUFBLENBQUMsS0FBSyxJQUFJLEVBQUU7O1lBQ1gsSUFBSTtnQkFDRixLQUFLLENBQUMsa0JBQWtCLEdBQUcsRUFBRSxDQUFDLENBQUE7Z0JBQzlCLHVFQUF1RTtnQkFDdkUsYUFBYTtnQkFDYixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxHQUFHLENBQUMsS0FBSSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsWUFBWSxDQUFBLEVBQUU7b0JBQ3hELGFBQWE7b0JBQ2IsT0FBTyxPQUFPLENBQUMsWUFBWSxDQUFBO2lCQUM1QjtnQkFDRCxNQUFNLENBQUMsR0FBd0I7b0JBQzdCLEdBQUcsRUFBRSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDO29CQUMvQixPQUFPLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUM7aUJBQ3BELENBQUE7Z0JBQ0Qsa0JBQWtCO2dCQUNsQixJQUFJLE1BQUEsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLE1BQU0sMENBQUUsT0FBTyxFQUFFO29CQUM1QixDQUFDLENBQUMsT0FBTyxHQUFHLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUE7aUJBQzdDO3FCQUFNO29CQUNMLGlEQUFpRDtvQkFDakQsdURBQXVEO29CQUN2RCwyQkFBMkI7b0JBQzNCLEVBQUU7b0JBQ0YsaURBQWlEO29CQUNqRCx5REFBeUQ7b0JBQ3pELDJDQUEyQztvQkFDM0MsQ0FBQyxDQUFDLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUE7aUJBQzNEO2dCQUNELHVCQUF1QjtnQkFDdkIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsR0FBRyxDQUFDLEVBQUU7b0JBQy9CLENBQUMsQ0FBQyxZQUFZLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtpQkFDNUI7Z0JBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFO29CQUNmLE1BQU0sU0FBUyxHQUFHLGNBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO29CQUNuRSxNQUFNLElBQUksR0FBRyxNQUFNLFNBQVMsQ0FDMUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBZ0IsRUFDcEMsQ0FBQyxDQUFDLE9BQU8sQ0FDVixDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO3dCQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUE7b0JBQ3RCLENBQUMsQ0FBQyxDQUFBO29CQUNGLENBQUMsQ0FBQyxPQUFPLEdBQUcsSUFBc0MsQ0FBQTtpQkFDbkQ7Z0JBQ0QsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFBO2dCQUN6QyxNQUFNLE9BQU8sR0FBRyxNQUFNLFVBQVUsQ0FBQyxTQUFTLENBQ3hDLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFDZCxFQUFFLElBQUksRUFBRSxDQUFDLEVBQUUsRUFDWDtvQkFDRSxNQUFNLEVBQUUsSUFBSTtvQkFDWixZQUFZLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUI7aUJBQ2pELENBQ0YsQ0FBQTtnQkFDRCxJQUFJLE9BQU8sQ0FBQyxhQUFhLEdBQUcsQ0FBQyxFQUFFO29CQUM3QixJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQTtpQkFDekI7cUJBQU07b0JBQ0wsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsR0FBRyxDQUFDLENBQUE7aUJBQ3pCO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFBO2FBQ3RCO1lBQUMsT0FBTyxLQUFLLEVBQUU7Z0JBQ2QsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUE7YUFDdkI7WUFDRCxPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQTtRQUN2QixDQUFDLENBQUMsRUFBRSxDQUFBO0lBQ04sQ0FBQztJQUVELEtBQUssQ0FDSCxHQUFXLEVBQ1gsT0FBc0QsRUFDdEQsV0FBK0IsSUFBSTtRQUVuQyxDQUFDO1FBQUEsQ0FBQyxLQUFLLElBQUksRUFBRTs7WUFDWCxJQUFJO2dCQUNGLEtBQUssQ0FBQyxvQkFBb0IsR0FBRyxFQUFFLENBQUMsQ0FBQTtnQkFDaEMsTUFBTSxZQUFZLEdBSWQsRUFBRSxDQUFBO2dCQUNOLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQTtnQkFDakQsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVk7b0JBQ3ZDLENBQUMsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLE9BQU8sRUFBRTtvQkFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtnQkFDTCxNQUFNLFdBQVcsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFBO2dCQUU5QiwrREFBK0Q7Z0JBQy9ELDREQUE0RDtnQkFDNUQsc0RBQXNEO2dCQUN0RCxJQUFJLFVBQVUsR0FBRyxDQUFDLElBQUksWUFBWSxHQUFHLENBQUMsRUFBRTtvQkFDdEMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE9BQU8sRUFBRSxHQUFHLFlBQVksQ0FBQTtvQkFDeEQsSUFBSSxXQUFXLEdBQUcsVUFBVSxFQUFFO3dCQUM1QixLQUFLLENBQUMseUJBQXlCLEdBQUcsRUFBRSxDQUFDLENBQUE7d0JBQ3JDLE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFBO3FCQUN0QjtvQkFDRCxZQUFZLENBQUMsWUFBWSxHQUFHLFdBQVcsQ0FBQTtpQkFDeEM7Z0JBRUQsSUFBSSxNQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxNQUFNLDBDQUFFLE9BQU8sRUFBRTtvQkFDNUIsWUFBWSxDQUFDLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFBO2lCQUN4RDtxQkFBTTtvQkFDTCxZQUFZLENBQUMsT0FBTyxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQTtpQkFDdEU7Z0JBQ0QsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFBO2dCQUN6QyxNQUFNLE9BQU8sR0FBRyxNQUFNLFVBQVUsQ0FBQyxTQUFTLENBQ3hDLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUNuQyxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsRUFDdEIsRUFBRSxZQUFZLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUNyRCxDQUFBO2dCQUNELElBQUksT0FBTyxDQUFDLFlBQVksS0FBSyxDQUFDLEVBQUU7b0JBQzlCLE9BQU8sUUFBUSxDQUFDLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUMsQ0FBQTtpQkFDbEU7cUJBQU07b0JBQ0wsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFBO29CQUNoQyxPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQTtpQkFDdEI7YUFDRjtZQUFDLE9BQU8sS0FBSyxFQUFFO2dCQUNkLE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFBO2FBQ3ZCO1FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtJQUNOLENBQUM7SUFFRDs7T0FFRztJQUNILEdBQUcsQ0FDRCxRQU1TO1FBRVQsQ0FBQztRQUFBLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDWCxJQUFJO2dCQUNGLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxDQUFBO2dCQUN6QixNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUE7Z0JBQ3pDLE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUM7b0JBQy9CLEdBQUcsRUFBRTt3QkFDSCxFQUFFLE9BQU8sRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRTt3QkFDL0IsRUFBRSxPQUFPLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUUsRUFBRSxFQUFFO3FCQUNqQztpQkFDRixDQUFDLENBQUE7Z0JBQ0YsTUFBTSxPQUFPLEdBQTBCLEVBQUUsQ0FBQTtnQkFDekMsSUFBSSxLQUFLLEVBQUUsTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFO29CQUNwQyxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxFQUFFO3dCQUMxQixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsT0FBeUMsQ0FBQyxDQUFBO3FCQUNyRTtvQkFDRCxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7aUJBQ25FO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFBO2dCQUN6QixRQUFRLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFBO2FBQ3hCO1lBQUMsT0FBTyxLQUFLLEVBQUU7Z0JBQ2QsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFBO2FBQ2hCO1FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtJQUNOLENBQUM7SUFFRDs7O09BR0c7SUFDSCxPQUFPLENBQUMsR0FBVyxFQUFFLFdBQStCLElBQUk7UUFDdEQsS0FBSyxDQUFDLHNCQUFzQixHQUFHLEVBQUUsQ0FBQyxDQUFBO1FBQ2xDLElBQUksQ0FBQyxXQUFXO2FBQ2IsSUFBSSxDQUFDLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FDbkIsVUFBVSxDQUFDLFNBQVMsQ0FDbEIsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQ25DLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FDckQsQ0FDRjthQUNBLElBQUksQ0FBQyxHQUFHLEVBQUU7WUFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxHQUFHLENBQUMsQ0FBQTtZQUN6QixRQUFRLENBQUMsSUFBSSxDQUFDLENBQUE7UUFDaEIsQ0FBQyxDQUFDO2FBQ0QsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsUUFBNEM7UUFDakQsS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUE7UUFDNUIsSUFBSSxDQUFDLFdBQVc7YUFDYixJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxjQUFjLEVBQUUsQ0FBQzthQUNqRCxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDL0IsYUFBYTthQUNaLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDbEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLFdBQStCLElBQUk7UUFDdkMsS0FBSyxDQUFDLG9CQUFvQixDQUFDLENBQUE7UUFDM0IsSUFBSSxDQUFDLFdBQVc7YUFDYixJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQzthQUN2QyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQzFCLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDbEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSztRQUNILEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxDQUFBO1FBQzNCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFBO0lBQzVDLENBQUM7Q0FDRjtBQW5hRCw2QkFtYUMifQ==
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 92c10975f..f43ed6ac9 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,6 +1,7 @@
+/* eslint-disable no-use-before-define */
import { exec } from 'child_process';
import { cyan, green, red, yellow } from 'colors';
-import { log_execution } from '../ActionUtilities';
+import { logExecution } from '../ActionUtilities';
import { Method } from '../RouteManager';
import RouteSubscriber from '../RouteSubscriber';
import { Search } from '../Search';
@@ -17,8 +18,10 @@ export class SearchManager extends ApiManager {
switch (action) {
case 'start':
case 'stop':
- const status = req.params.action === 'start';
- SolrManager.SetRunning(status);
+ {
+ const status = req.params.action === 'start';
+ SolrManager.SetRunning(status);
+ }
break;
case 'update':
await SolrManager.update();
@@ -66,13 +69,13 @@ export namespace SolrManager {
export async function update() {
console.log(green('Beginning update...'));
- await log_execution<void>({
+ await logExecution<void>({
startMessage: 'Clearing existing Solr information...',
endMessage: 'Solr information successfully cleared',
action: Search.clear,
color: cyan,
});
- const cursor = await log_execution({
+ const cursor = await logExecution({
startMessage: 'Connecting to and querying for all documents from database...',
endMessage: ({ result, error }) => {
const success = error === null && result !== undefined;
@@ -95,7 +98,7 @@ export namespace SolrManager {
if (doc.__type !== 'Doc') {
return;
}
- const fields = doc.fields;
+ const { fields } = doc;
if (!fields) {
return;
}
@@ -118,7 +121,7 @@ export namespace SolrManager {
}
}
await cursor?.forEach(updateDoc);
- const result = await log_execution({
+ const result = await logExecution({
startMessage: `Dispatching updates for ${updates.length} documents`,
endMessage: 'Dispatched updates complete',
action: () => Search.updateDocuments(updates),
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index e37f8c6db..bebe50a62 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -1,67 +1,64 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied, AuthorizedCore, SecureHandler } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
-import { sessionAgent } from "..";
-import { DashSessionAgent } from "../DashSession/DashSessionAgent";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _permissionDenied, AuthorizedCore, SecureHandler } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { sessionAgent } from '..';
+import { DashSessionAgent } from '../DashSession/DashSessionAgent';
-const permissionError = "You are not authorized!";
+const permissionError = 'You are not authorized!';
export default class SessionManager extends ApiManager {
+ private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add('session_key', ...params);
- private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add("session_key", ...params);
-
- private authorizedAction = (handler: SecureHandler) => {
- return (core: AuthorizedCore) => {
- const { req: { params }, res } = core;
- if (!process.env.MONITORED) {
- return res.send("This command only makes sense in the context of a monitored session.");
- }
- if (params.session_key !== process.env.session_key) {
- return _permission_denied(res, permissionError);
- }
- return handler(core);
- };
- }
+ private authorizedAction = (handler: SecureHandler) => (core: AuthorizedCore) => {
+ const {
+ req: { params },
+ res,
+ } = core;
+ if (!process.env.MONITORED) {
+ return res.send('This command only makes sense in the context of a monitored session.');
+ }
+ if (params.session_key !== process.env.session_key) {
+ return _permissionDenied(res, permissionError);
+ }
+ return handler(core);
+ };
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: this.secureSubscriber("debug", "to?"),
+ subscription: this.secureSubscriber('debug', 'to?'),
secureHandler: this.authorizedAction(async ({ req: { params }, res }) => {
const to = params.to || DashSessionAgent.notificationRecipient;
- const { error } = await sessionAgent.serverWorker.emit("debug", { to });
+ const { error } = await sessionAgent.serverWorker.emit('debug', { to });
res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`);
- })
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("backup"),
+ subscription: this.secureSubscriber('backup'),
secureHandler: this.authorizedAction(async ({ res }) => {
- const { error } = await sessionAgent.serverWorker.emit("backup");
- res.send(error ? error.message : "Your request was successful: the server successfully created a new back up.");
- })
+ const { error } = await sessionAgent.serverWorker.emit('backup');
+ res.send(error ? error.message : 'Your request was successful: the server successfully created a new back up.');
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("kill"),
+ subscription: this.secureSubscriber('kill'),
secureHandler: this.authorizedAction(({ res }) => {
- res.send("Your request was successful: the server and its session have been killed.");
- sessionAgent.killSession("an authorized user has manually ended the server session via the /kill route");
- })
+ res.send('Your request was successful: the server and its session have been killed.');
+ sessionAgent.killSession('an authorized user has manually ended the server session via the /kill route');
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("deleteSession"),
+ subscription: this.secureSubscriber('deleteSession'),
secureHandler: this.authorizedAction(async ({ res }) => {
- const { error } = await sessionAgent.serverWorker.emit("delete");
- res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home.");
- })
+ const { error } = await sessionAgent.serverWorker.emit('delete');
+ res.send(error ? error.message : 'Your request was successful: the server successfully deleted the database. Return to /home.');
+ }),
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 2306b6589..4cb3d8baf 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -1,50 +1,27 @@
+import * as AdmZip from 'adm-zip';
import * as formidable from 'formidable';
-import { createReadStream, createWriteStream, unlink, writeFile } from 'fs';
-import * as path from 'path';
+import * as fs from 'fs';
+import { createReadStream, createWriteStream, unlink } from 'fs';
+import * as imageDataUri from 'image-data-uri';
import Jimp from 'jimp';
-import { filesDirectory, publicDirectory } from '..';
+import * as path from 'path';
+import * as uuid from 'uuid';
import { retrocycle } from '../../decycler/decycler';
+import { DashVersion } from '../../fields/DocSymbols';
import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
-import { Database } from '../database';
import { Method, _success } from '../RouteManager';
-import RouteSubscriber from '../RouteSubscriber';
import { AcceptableMedia, Upload } from '../SharedMediaTypes';
+import { clientPathToFile, Directory, pathToDirectory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
import ApiManager, { Registration } from './ApiManager';
import { SolrManager } from './SearchManager';
-import * as uuid from 'uuid';
-import { DashVersion } from '../../fields/DocSymbols';
-import * as AdmZip from 'adm-zip';
-import * as imageDataUri from 'image-data-uri';
-import * as fs from 'fs';
-
-export enum Directory {
- parsed_files = 'parsed_files',
- images = 'images',
- videos = 'videos',
- pdfs = 'pdfs',
- text = 'text',
- audio = 'audio',
- csv = 'csv',
-}
-
-export function serverPathToFile(directory: Directory, filename: string) {
- return path.normalize(`${filesDirectory}/${directory}/${filename}`);
-}
-
-export function pathToDirectory(directory: Directory) {
- return path.normalize(`${filesDirectory}/${directory}`);
-}
-
-export function clientPathToFile(directory: Directory, filename: string) {
- return `/files/${directory}/${filename}`;
-}
export default class UploadManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.POST,
subscription: '/ping',
- secureHandler: async ({ req, res }) => {
+ secureHandler: async ({ /* req, */ res }) => {
_success(res, { message: DashVersion, date: new Date() });
},
});
@@ -78,31 +55,33 @@ export default class UploadManager extends ApiManager {
form.on('progress', e => fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `read:(${Math.round((100 * +e) / +filesize)}%) ${e} of ${filesize}`)));
return new Promise<void>(resolve => {
form.parse(req, async (_err, _fields, files) => {
- const results: Upload.FileResponse[] = [];
if (_err?.message) {
- results.push({
- source: {
- filepath: '',
- originalFilename: 'none',
- newFilename: 'none',
- mimetype: 'text',
- size: 0,
- hashAlgorithm: 'md5',
- toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+ _success(res, [
+ {
+ source: {
+ filepath: '',
+ originalFilename: 'none',
+ newFilename: 'none',
+ mimetype: 'text',
+ size: 0,
+ hashAlgorithm: 'md5',
+ toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+ },
+ result: { name: 'failed upload', message: `${_err.message}` },
},
- result: { name: 'failed upload', message: `${_err.message}` },
- });
- }
- fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+ ]);
+ } else {
+ fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+ const results = (
+ await Promise.all(
+ Array.from(Object.keys(files)).map(
+ async key => (!files[key] ? undefined : DashUploadUtils.upload(files[key]![0] /* , key */)) // key is the guid used by the client to track upload progress.
+ )
+ )
+ ).filter(result => result && !(result.result instanceof Error));
- for (const key in files) {
- const f = files[key];
- if (f) {
- const result = await DashUploadUtils.upload(f[0], key); // key is the guid used by the client to track upload progress.
- result && !(result.result instanceof Error) && results.push(result);
- }
+ _success(res, results);
}
- _success(res, results);
resolve();
});
});
@@ -113,17 +92,14 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/uploadYoutubeVideo',
secureHandler: async ({ req, res }) => {
- //req.readableBuffer.head.data
- return new Promise<void>(async resolve => {
- req.addListener('data', async args => {
- const payload = String.fromCharCode.apply(String, args);
- const { videoId, overwriteId } = JSON.parse(payload);
- const results: Upload.FileResponse[] = [];
- const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
- result && results.push(result);
- _success(res, results);
- resolve();
- });
+ // req.readableBuffer.head.data
+ req.addListener('data', async args => {
+ const payload = String.fromCharCode(...args); // .apply(String, args);
+ const { videoId, overwriteId } = JSON.parse(payload);
+ const results: Upload.FileResponse[] = [];
+ const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
+ result && results.push(result);
+ _success(res, results);
});
},
});
@@ -132,49 +108,10 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/queryYoutubeProgress',
secureHandler: async ({ req, res }) => {
- return new Promise<void>(async resolve => {
- req.addListener('data', args => {
- const payload = String.fromCharCode.apply(String, args);
- const videoId = JSON.parse(payload).videoId;
- _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId, req.user) });
- resolve();
- });
- });
- },
- });
-
- register({
- method: Method.POST,
- subscription: new RouteSubscriber('youtubeScreenshot'),
- secureHandler: async ({ req, res }) => {
- const { id, timecode } = req.body;
- const convert = (raw: string) => {
- const number = Math.floor(Number(raw));
- const seconds = number % 60;
- const minutes = (number - seconds) / 60;
- return `${minutes}m${seconds}s`;
- };
- const suffix = timecode ? `&t=${convert(timecode)}` : ``;
- const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`;
- const buffer = await captureYoutubeScreenshot(targetUrl);
- if (!buffer) {
- return res.send();
- }
- const resolvedName = `youtube_capture_${id}_${suffix}.png`;
- const resolvedPath = serverPathToFile(Directory.images, resolvedName);
- return new Promise<void>(resolve => {
- writeFile(resolvedPath, buffer, async error => {
- if (error) {
- return res.send();
- }
- await DashUploadUtils.outputResizedImages(resolvedPath, resolvedName, pathToDirectory(Directory.images));
- res.send({
- accessPaths: {
- agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName),
- },
- } as Upload.FileInformation);
- resolve();
- });
+ req.addListener('data', args => {
+ const payload = String.fromCharCode(...args); // .apply(String, args);
+ const { videoId } = JSON.parse(payload);
+ _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId) });
});
},
});
@@ -186,7 +123,8 @@ export default class UploadManager extends ApiManager {
const { sources } = req.body;
if (Array.isArray(sources)) {
const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
- return res.send(results);
+ res.send(results);
+ return;
}
res.send();
},
@@ -203,20 +141,22 @@ export default class UploadManager extends ApiManager {
const getId = (id: string): string => {
if (!remap || id.endsWith('Proto')) return id;
if (id in ids) return ids[id];
- return (ids[id] = uuid.v4());
+ ids[id] = uuid.v4();
+ return ids[id];
};
- const mapFn = (doc: any) => {
+ const mapFn = (docIn: any) => {
+ const doc = docIn;
if (doc.id) {
doc.id = getId(doc.id);
}
+ // eslint-disable-next-line no-restricted-syntax
for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) {
- continue;
- }
+ // eslint-disable-next-line no-continue
+ if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
const field = doc.fields[key];
- if (field === undefined || field === null) {
- continue;
- }
+ // eslint-disable-next-line no-continue
+ if (field === undefined || field === null) continue;
if (field.__type === 'Doc') {
mapFn(field);
@@ -229,78 +169,80 @@ export default class UploadManager extends ApiManager {
} else if (field.__type === 'list') {
mapFn(field);
} else if (typeof field === 'string') {
- const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
- doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
- return `${p1}${getId(p2)}"`;
- });
+ const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w-]*)"/g;
+ doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
} else if (field.__type === 'RichTextField') {
const re = /("href"\s*:\s*")(.*?)"/g;
- field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
- return `${p1}${getId(p2)}"`;
- });
+ field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
}
}
};
return new Promise<void>(resolve => {
form.parse(req, async (_err, fields, files) => {
- remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); //.remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
+ remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); // .remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
let id: string = '';
let docids: string[] = [];
let linkids: string[] = [];
try {
- for (const name in files) {
- const f = files[name];
- if (!f) continue;
- const path_2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
- const zip = new AdmZip(path_2.filepath);
- zip.getEntries().forEach((entry: any) => {
- let entryName = entry.entryName.replace(/%%%/g, '/');
- if (!entryName.startsWith('files/')) {
- return;
- }
- const extension = path.extname(entryName);
- const pathname = publicDirectory + '/' + entry.entryName;
- const targetname = publicDirectory + '/' + entryName;
- try {
- zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
- createReadStream(pathname).pipe(createWriteStream(targetname));
- Jimp.read(pathname).then(img => {
- DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
- const outputPath = InjectSize(targetname, suffix);
- if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
- else img = img.resize(width, Jimp.AUTO).write(outputPath);
+ // eslint-disable-next-line no-restricted-syntax
+ for (const name in Object.keys(files)) {
+ if (Object.prototype.hasOwnProperty.call(files, name)) {
+ const f = files[name];
+ // eslint-disable-next-line no-continue
+ if (!f) continue;
+ const path2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
+ const zip = new AdmZip(path2.filepath);
+ zip.getEntries().forEach((entry: any) => {
+ const entryName = entry.entryName.replace(/%%%/g, '/');
+ if (!entryName.startsWith('files/')) {
+ return;
+ }
+ const extension = path.extname(entryName);
+ const pathname = publicDirectory + '/' + entry.entryName;
+ const targetname = publicDirectory + '/' + entryName;
+ try {
+ zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
+ createReadStream(pathname).pipe(createWriteStream(targetname));
+ Jimp.read(pathname).then(imgIn => {
+ let img = imgIn;
+ DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
+ const outputPath = InjectSize(targetname, suffix);
+ if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
+ else img = img.resize(width, Jimp.AUTO).write(outputPath);
+ });
+ unlink(pathname, () => {});
});
- unlink(pathname, () => {});
- });
- } catch (e) {
- console.log(e);
- }
- });
- const json = zip.getEntry('docs.json');
- if (json) {
- try {
- const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
- const { docs, links } = data;
- id = getId(data.id);
- const rdocs = Object.keys(docs).map(key => docs[key]);
- const ldocs = Object.keys(links).map(key => links[key]);
- [...rdocs, ...ldocs].forEach(mapFn);
- docids = rdocs.map(doc => doc.id);
- linkids = ldocs.map(link => link.id);
- await Promise.all(
- [...rdocs, ...ldocs].map(
- doc =>
- new Promise<void>(res => {
- // overwrite mongo doc with json doc contents
- Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true);
- })
- )
- );
- } catch (e) {
- console.log(e);
+ } catch (e) {
+ console.log(e);
+ }
+ });
+ const json = zip.getEntry('docs.json');
+ if (json) {
+ try {
+ const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
+ const { docs, links } = data;
+ id = getId(data.id);
+ const rdocs = Object.keys(docs).map(key => docs[key]);
+ const ldocs = Object.keys(links).map(key => links[key]);
+ [...rdocs, ...ldocs].forEach(mapFn);
+ docids = rdocs.map(doc => doc.id);
+ linkids = ldocs.map(link => link.id);
+ // eslint-disable-next-line no-await-in-loop
+ await Promise.all(
+ [...rdocs, ...ldocs].map(
+ doc =>
+ new Promise<void>(dbRes => {
+ // overwrite mongo doc with json doc contents
+ Database.Instance.replace(doc.id, doc, err => dbRes(err && console.log(err)), true);
+ })
+ )
+ );
+ } catch (e) {
+ console.log(e);
+ }
}
+ unlink(path2.filepath, () => {});
}
- unlink(path_2.filepath, () => {});
}
SolrManager.update();
res.send(JSON.stringify({ id, docids, linkids } || 'error'));
@@ -319,9 +261,8 @@ export default class UploadManager extends ApiManager {
secureHandler: async ({ req, res }) => {
const { source } = req.body;
if (typeof source === 'string') {
- return res.send(await DashUploadUtils.InspectImage(source));
- }
- res.send({});
+ res.send(await DashUploadUtils.InspectImage(source));
+ } else res.send({});
},
});
@@ -329,7 +270,7 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/uploadURI',
secureHandler: ({ req, res }) => {
- const uri: any = req.body.uri;
+ const { uri } = req.body;
const filename = req.body.name;
const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
const deleteFiles = req.body.replaceRootFilename;
@@ -338,23 +279,24 @@ export default class UploadManager extends ApiManager {
return;
}
if (deleteFiles) {
- const path = serverPathToFile(Directory.images, '');
+ const serverPath = serverPathToFile(Directory.images, '');
const regex = new RegExp(`${deleteFiles}.*`);
- fs.readdirSync(path)
+ fs.readdirSync(serverPath)
.filter((f: any) => regex.test(f))
- .map((f: any) => fs.unlinkSync(path + f));
+ .map((f: any) => fs.unlinkSync(serverPath + f));
}
- return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
+ imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = path.extname(savedName).toLowerCase();
if (AcceptableMedia.imageFormats.includes(ext)) {
- Jimp.read(savedName).then(img =>
+ Jimp.read(savedName).then(imgIn => {
+ let img = imgIn;
(!origSuffix ? [{ width: 400, suffix: SizeSuffix.Medium }] : Object.values(DashUploadUtils.Sizes)) //
.forEach(({ width, suffix }) => {
const outputPath = serverPathToFile(Directory.images, InjectSize(filename, suffix) + ext);
if (!width) createReadStream(savedName).pipe(createWriteStream(outputPath));
else img = img.resize(width, Jimp.AUTO).write(outputPath);
- })
- );
+ });
+ });
}
res.send(clientPathToFile(Directory.images, filename + ext));
});
@@ -362,35 +304,3 @@ export default class UploadManager extends ApiManager {
});
}
}
-function delay(ms: number) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-/**
- * On success, returns a buffer containing the bytes of a screenshot
- * of the video (optionally, at a timecode) specified by @param targetUrl.
- *
- * On failure, returns undefined.
- */
-async function captureYoutubeScreenshot(targetUrl: string) {
- // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
- // const page = await browser.newPage();
- // // await page.setViewport({ width: 1920, height: 1080 });
-
- // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any });
-
- // const videoPlayer = await page.$('.html5-video-player');
- // videoPlayer && await page.focus("video");
- // await delay(7000);
- // const ad = await page.$('.ytp-ad-skip-button-text');
- // await ad?.click();
- // await videoPlayer?.click();
- // await delay(1000);
- // // hide youtube player controls.
- // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none');
-
- // const buffer = await videoPlayer?.screenshot({ encoding: "binary" });
- // await browser.close();
-
- // return buffer;
- return null;
-}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 0431b9bcf..b587340e2 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,16 +1,14 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method } from '../RouteManager';
-import { Database } from '../database';
-import { msToTime } from '../ActionUtilities';
import * as bcrypt from 'bcrypt-nodejs';
+import { check, validationResult } from 'express-validator';
+import { Utils } from '../../Utils';
import { Opt } from '../../fields/Doc';
-import { WebSocket } from '../websocket';
-import { resolvedPorts } from '../server_Initialization';
import { DashVersion } from '../../fields/DocSymbols';
-import { Utils } from '../../Utils';
-import { check, validationResult } from 'express-validator';
+import { msToTime } from '../ActionUtilities';
+import { Method } from '../RouteManager';
+import { resolvedPorts, socketMap, timeMap } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
-export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
user: string;
duration: number;
@@ -32,9 +30,10 @@ export default class UserManager extends ApiManager {
method: Method.POST,
subscription: '/setCacheDocumentIds',
secureHandler: async ({ user, req, res }) => {
+ const userModel = user;
const result: any = {};
- user.cacheDocumentIds = req.body.cacheDocumentIds;
- user.save().then(undefined, err => {
+ userModel.cacheDocumentIds = req.body.cacheDocumentIds;
+ userModel.save().then(undefined, (err: any) => {
if (err) {
result.error = [{ msg: 'Error while caching documents' }];
}
@@ -90,17 +89,19 @@ export default class UserManager extends ApiManager {
method: Method.POST,
subscription: '/internalResetPassword',
secureHandler: async ({ user, req, res }) => {
+ const userModel = user;
const result: any = {};
- const { curr_pass, new_pass, new_confirm } = req.body;
+ // eslint-disable-next-line camelcase
+ const { curr_pass, new_pass } = req.body;
// perhaps should assert whether curr password is entered correctly
const validated = await new Promise<Opt<boolean>>(resolve => {
- bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
- if (err || !passwords_match) {
+ bcrypt.compare(curr_pass, userModel.password, (err, passwordsMatch) => {
+ if (err || !passwordsMatch) {
result.error = [{ msg: 'Incorrect current password' }];
res.send(result);
resolve(undefined);
} else {
- resolve(passwords_match);
+ resolve(passwordsMatch);
}
});
});
@@ -111,10 +112,11 @@ export default class UserManager extends ApiManager {
check('new_pass', 'Password must be at least 4 characters long')
.run(req)
- .then(chcekcres => console.log(chcekcres)); //.len({ min: 4 });
+ .then(chcekcres => console.log(chcekcres)); // .len({ min: 4 });
check('new_confirm', 'Passwords do not match')
.run(req)
- .then(theres => console.log(theres)); //.equals(new_pass);
+ .then(theres => console.log(theres)); // .equals(new_pass);
+ // eslint-disable-next-line camelcase
if (curr_pass === new_pass) {
result.error = [{ msg: 'Current and new password are the same' }];
}
@@ -125,12 +127,13 @@ export default class UserManager extends ApiManager {
// will only change password if there are no errors.
if (!result.error) {
- user.password = new_pass;
- user.passwordResetToken = undefined;
- user.passwordResetExpires = undefined;
+ // eslint-disable-next-line camelcase
+ userModel.password = new_pass;
+ userModel.passwordResetToken = undefined;
+ userModel.passwordResetExpires = undefined;
}
- user.save().then(undefined, err => {
+ userModel.save().then(undefined, err => {
if (err) {
result.error = [{ msg: 'Error while saving new password' }];
}
@@ -149,13 +152,16 @@ export default class UserManager extends ApiManager {
const activeTimes: ActivityUnit[] = [];
const inactiveTimes: ActivityUnit[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const user in timeMap) {
- const time = timeMap[user];
- const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
- if (socketPair && !socketPair[0].disconnected) {
- const duration = now - time;
- const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
- target.push({ user, duration });
+ if (Object.prototype.hasOwnProperty.call(timeMap, user)) {
+ const time = timeMap[user];
+ const socketPair = Array.from(socketMap).find(pair => pair[1] === user);
+ if (socketPair && !socketPair[0].disconnected) {
+ const duration = now - time;
+ const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
+ target.push({ user, duration });
+ }
}
}
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e657866ce..8ad421a30 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,6 +1,7 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
import { exec } from 'child_process';
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+
// import { IBM_Recommender } from "../../client/apis/IBM_Recommender";
// import { Recommender } from "../Recommender";
@@ -8,9 +9,7 @@ import { exec } from 'child_process';
// recommender.testModel();
export default class UtilManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
// register({
// method: Method.POST,
// subscription: "/IBMAnalysis",
@@ -33,26 +32,25 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
- subscription: "/pull",
- secureHandler: async ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/pull',
+ secureHandler: async ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', err => {
if (err) {
res.send(err.message);
return;
}
- res.redirect("/");
+ res.redirect('/');
resolve();
});
- });
- }
+ }),
});
register({
method: Method.GET,
- subscription: "/version",
- secureHandler: ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/version',
+ secureHandler: ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout) => {
if (err) {
res.send(err.message);
@@ -61,10 +59,7 @@ export default class UtilManager extends ApiManager {
res.send(stdout);
});
resolve();
- });
- }
+ }),
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/Client.ts b/src/server/Client.ts
index e6f953712..f67999c5b 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed } from 'mobx';
export class Client {
private _guid: string;
@@ -7,5 +7,7 @@ export class Client {
this._guid = guid;
}
- @computed public get GUID(): string { return this._guid; }
-} \ No newline at end of file
+ @computed public get GUID(): string {
+ return this._guid;
+ }
+}
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 1ef7a131d..f937c17ad 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -1,18 +1,19 @@
-import { Email, pathFromRoot } from '../ActionUtilities';
-import { red, yellow, green, cyan } from 'colors';
-import { get } from 'request-promise';
-import { Utils } from '../../Utils';
-import { WebSocket } from '../websocket';
-import { MessageStore } from '../Message';
-import { launchServer, onWindows } from '..';
-import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from 'fs';
import * as Archiver from 'archiver';
+import { cyan, green, red, yellow } from 'colors';
+import { createWriteStream, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
import { resolve } from 'path';
+import { get } from 'request-promise';
import { rimraf } from 'rimraf';
+import { launchServer, onWindows } from '..';
+import { Utils } from '../../Utils';
+import { ServerUtils } from '../../ServerUtils';
+import { Email, pathFromRoot } from '../ActionUtilities';
+import { MessageStore } from '../Message';
+import { WebSocket } from '../websocket';
import { AppliedSessionAgent, ExitHandler } from './Session/agents/applied_session_agent';
-import { ServerWorker } from './Session/agents/server_worker';
import { Monitor } from './Session/agents/monitor';
-import { MessageHandler, ErrorLike } from './Session/agents/promisified_ipc_manager';
+import { ErrorLike, MessageHandler } from './Session/agents/promisified_ipc_manager';
+import { ServerWorker } from './Session/agents/server_worker';
/**
* If we're the monitor (master) thread, we should launch the monitor logic for the session.
@@ -22,6 +23,7 @@ import { MessageHandler, ErrorLike } from './Session/agents/promisified_ipc_mana
export class DashSessionAgent extends AppliedSessionAgent {
private readonly signature = '-Dash Server Session Manager';
private readonly releaseDesktop = pathFromRoot('../../Desktop');
+ public static notificationRecipient = 'browndashptc@gmail.com';
/**
* The core method invoked when the single master thread is initialized.
@@ -149,7 +151,7 @@ export class DashSessionAgent extends AppliedSessionAgent {
const { _socket } = WebSocket;
if (_socket) {
const message = typeof reason === 'boolean' ? (reason ? 'exit' : 'temporary') : 'crash';
- Utils.Emit(_socket, MessageStore.ConnectionTerminated, message);
+ ServerUtils.Emit(_socket, MessageStore.ConnectionTerminated, message);
}
};
@@ -217,7 +219,3 @@ export class DashSessionAgent extends AppliedSessionAgent {
}
}
}
-
-export namespace DashSessionAgent {
- export const notificationRecipient = 'browndashptc@gmail.com';
-}
diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts
index 2037e93e5..c42ba95cc 100644
--- a/src/server/DashSession/Session/agents/applied_session_agent.ts
+++ b/src/server/DashSession/Session/agents/applied_session_agent.ts
@@ -1,13 +1,13 @@
-import * as _cluster from "cluster";
-import { Monitor } from "./monitor";
-import { ServerWorker } from "./server_worker";
+import * as _cluster from 'cluster';
+import { Monitor } from './monitor';
+import { ServerWorker } from './server_worker';
+
const cluster = _cluster as any;
const isMaster = cluster.isPrimary;
export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
export abstract class AppliedSessionAgent {
-
// the following two methods allow the developer to create a custom
// session and use the built in customization options for each thread
protected abstract initializeMonitor(monitor: Monitor): Promise<string>;
@@ -18,15 +18,15 @@ export abstract class AppliedSessionAgent {
public killSession = (reason: string, graceful = true, errorCode = 0) => {
const target = cluster.default.isPrimary ? this.sessionMonitor : this.serverWorker;
target.killSession(reason, graceful, errorCode);
- }
+ };
private sessionMonitorRef: Monitor | undefined;
public get sessionMonitor(): Monitor {
if (!cluster.default.isPrimary) {
- this.serverWorker.emit("kill", {
+ this.serverWorker.emit('kill', {
graceful: false,
- reason: "Cannot access the session monitor directly from the server worker thread.",
- errorCode: 1
+ reason: 'Cannot access the session monitor directly from the server worker thread.',
+ errorCode: 1,
});
throw new Error();
}
@@ -36,7 +36,7 @@ export abstract class AppliedSessionAgent {
private serverWorkerRef: ServerWorker | undefined;
public get serverWorker(): ServerWorker {
if (isMaster) {
- throw new Error("Cannot access the server worker directly from the session monitor thread");
+ throw new Error('Cannot access the server worker directly from the session monitor thread');
}
return this.serverWorkerRef!;
}
@@ -52,8 +52,7 @@ export abstract class AppliedSessionAgent {
this.serverWorkerRef = await this.initializeServerWorker();
}
} else {
- throw new Error("Cannot launch a session thread more than once per process.");
+ throw new Error('Cannot launch a session thread more than once per process.');
}
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/monitor.ts b/src/server/DashSession/Session/agents/monitor.ts
index a6fde4356..6cdad46c2 100644
--- a/src/server/DashSession/Session/agents/monitor.ts
+++ b/src/server/DashSession/Session/agents/monitor.ts
@@ -1,21 +1,19 @@
-import { ExitHandler } from './applied_session_agent';
-import { Configuration, configurationSchema, defaultConfig, Identifiers, colorMapping } from '../utilities/session_config';
-import Repl, { ReplAction } from '../utilities/repl';
+import { ExecOptions, exec } from 'child_process';
import * as _cluster from 'cluster';
import { Worker } from 'cluster';
-import { manage, MessageHandler, ErrorLike } from './promisified_ipc_manager';
-import { red, cyan, white, yellow, blue } from 'colors';
-import { exec, ExecOptions } from 'child_process';
-import { validate, ValidationError } from 'jsonschema';
-import { Utilities } from '../utilities/utilities';
+import { blue, cyan, red, white, yellow } from 'colors';
import { readFileSync } from 'fs';
+import { ValidationError, validate } from 'jsonschema';
+import Repl, { ReplAction } from '../utilities/repl';
+import { Configuration, Identifiers, colorMapping, configurationSchema, defaultConfig } from '../utilities/session_config';
+import { Utilities } from '../utilities/utilities';
+import { ExitHandler } from './applied_session_agent';
import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, MessageHandler, manage } from './promisified_ipc_manager';
import { ServerWorker } from './server_worker';
+
const cluster = _cluster as any;
-const isWorker = cluster.isWorker;
-const setupMaster = cluster.setupPrimary;
-const on = cluster.on;
-const fork = cluster.fork;
+const { isWorker, setupMaster, on, fork } = cluster;
/**
* Validates and reads the configuration file, accordingly builds a child process factory
@@ -41,9 +39,8 @@ export class Monitor extends IPCMessageReceiver {
} else if (++Monitor.count > 1) {
console.error(red('cannot create more than one monitor.'));
process.exit(1);
- } else {
- return new Monitor();
}
+ return new Monitor();
}
private constructor() {
@@ -128,25 +125,25 @@ export class Monitor extends IPCMessageReceiver {
this.repl.registerCommand(basename, argPatterns, action);
};
- public exec = (command: string, options?: ExecOptions) => {
- return new Promise<void>(resolve => {
+ public exec = (command: string, options?: ExecOptions) =>
+ new Promise<void>(resolve => {
exec(command, { ...options, encoding: 'utf8' }, (error, stdout, stderr) => {
if (error) {
this.execLog(red(`unable to execute ${white(command)}`));
error.message.split('\n').forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
} else {
- let outLines: string[], errorLines: string[];
- if ((outLines = stdout.split('\n').filter(line => line.length)).length) {
+ const outLines = stdout.split('\n').filter(line => line.length);
+ if (outLines.length) {
outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
}
- if ((errorLines = stderr.split('\n').filter(line => line.length)).length) {
+ const errorLines = stderr.split('\n').filter(line => line.length);
+ if (errorLines.length) {
errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
}
}
resolve();
});
});
- };
/**
* Generates a blue UTC string associated with the time
@@ -226,12 +223,10 @@ export class Monitor extends IPCMessageReceiver {
const newPollingIntervalSeconds = Math.floor(Number(args[1]));
if (newPollingIntervalSeconds < 0) {
this.mainLog(red('the polling interval must be a non-negative integer'));
- } else {
- if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
- this.config.polling.intervalSeconds = newPollingIntervalSeconds;
- if (args[2] === 'true') {
- Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
- }
+ } else if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
+ this.config.polling.intervalSeconds = newPollingIntervalSeconds;
+ if (args[2] === 'true') {
+ Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
}
}
});
@@ -297,6 +292,7 @@ export class Monitor extends IPCMessageReceiver {
};
}
+// eslint-disable-next-line no-redeclare
export namespace Monitor {
export enum IntrinsicEvents {
KeyGenerated = 'key_generated',
diff --git a/src/server/DashSession/Session/agents/process_message_router.ts b/src/server/DashSession/Session/agents/process_message_router.ts
index 0745ea455..3e2b7d8d0 100644
--- a/src/server/DashSession/Session/agents/process_message_router.ts
+++ b/src/server/DashSession/Session/agents/process_message_router.ts
@@ -1,7 +1,6 @@
-import { MessageHandler, PromisifiedIPCManager, HandlerMap } from "./promisified_ipc_manager";
+import { MessageHandler, PromisifiedIPCManager, HandlerMap } from './promisified_ipc_manager';
export default abstract class IPCMessageReceiver {
-
protected static IPCManager: PromisifiedIPCManager;
protected handlers: HandlerMap = {};
@@ -18,7 +17,7 @@ export default abstract class IPCMessageReceiver {
} else {
handlers.push(handler);
}
- }
+ };
/**
* Unregister a given listener at this message.
@@ -31,11 +30,10 @@ export default abstract class IPCMessageReceiver {
handlers.splice(index, 1);
}
}
- }
+ };
- /**
+ /**
* Unregister all listeners at this message.
*/
public clearMessageListeners = (...names: string[]) => names.map(name => delete this.handlers[name]);
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
index 76e218977..99b4d4de3 100644
--- a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
+++ b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
@@ -1,13 +1,14 @@
-import { Utilities } from '../utilities/utilities';
import { ChildProcess } from 'child_process';
+import { Utilities } from '../utilities/utilities';
/**
- * Convenience constructor
- * @param target the process / worker to which to attach the specialized listeners
+ * Specifies a general message format for this API
*/
-export function manage(target: IPCTarget, handlers?: HandlerMap) {
- return new PromisifiedIPCManager(target, handlers);
-}
+export type Message<T = any> = {
+ name: string;
+ args?: T;
+};
+export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
/**
* Captures the logic to execute upon receiving a message
@@ -22,15 +23,10 @@ export type HandlerMap = { [name: string]: MessageHandler[] };
*/
export type IPCTarget = NodeJS.Process | ChildProcess;
-/**
- * Specifies a general message format for this API
- */
-export type Message<T = any> = {
- name: string;
- args?: T;
-};
-export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
-
+interface Metadata {
+ isResponse: boolean;
+ id: string;
+}
/**
* When a message is emitted, it is embedded with private metadata
* to facilitate the resolution of promises, etc.
@@ -38,10 +34,6 @@ export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
interface InternalMessage extends Message {
metadata: Metadata;
}
-interface Metadata {
- isResponse: boolean;
- id: string;
-}
/**
* Allows for the transmission of the error's key features over IPC.
@@ -95,7 +87,7 @@ export class PromisifiedIPCManager {
}
return new Promise<Response<T>>(resolve => {
const messageId = Utilities.guid();
- type InternalMessageHandler = (message: any /* MessageListener*/) => any | Promise<any>;
+ type InternalMessageHandler = (message: any /* MessageListener */) => any | Promise<any>;
const responseHandler: InternalMessageHandler = ({ metadata: { id, isResponse }, args }) => {
if (isResponse && id === messageId) {
this.target.removeListener('message', responseHandler);
@@ -118,8 +110,8 @@ export class PromisifiedIPCManager {
* completion response for each of the pending messages, allowing their
* promises in the caller to resolve.
*/
- public destroy = () => {
- return new Promise<void>(async resolve => {
+ public destroy = () =>
+ new Promise<void>(async resolve => {
if (this.callerIsTarget) {
this.destroyHelper();
} else {
@@ -127,7 +119,6 @@ export class PromisifiedIPCManager {
}
resolve();
});
- };
/**
* Dispatches the dummy responses and sets the isDestroyed flag to true.
@@ -168,12 +159,20 @@ export class PromisifiedIPCManager {
error = e;
}
if (!this.isDestroyed && this.target.send) {
- const metadata = { id, isResponse: true };
+ const metadataRes = { id, isResponse: true };
const response: Response = { results, error };
- const message = { name, args: response, metadata };
+ const messageRes = { name, args: response, metadata: metadataRes };
delete this.pendingMessages[id];
- this.target.send(message);
+ this.target.send(messageRes);
}
}
};
}
+
+/**
+ * Convenience constructor
+ * @param target the process / worker to which to attach the specialized listeners
+ */
+export function manage(target: IPCTarget, handlers?: HandlerMap) {
+ return new PromisifiedIPCManager(target, handlers);
+}
diff --git a/src/server/DashSession/Session/agents/server_worker.ts b/src/server/DashSession/Session/agents/server_worker.ts
index d8b3ee80b..85e1b31d6 100644
--- a/src/server/DashSession/Session/agents/server_worker.ts
+++ b/src/server/DashSession/Session/agents/server_worker.ts
@@ -1,10 +1,10 @@
-import cluster from "cluster";
-import { green, red, white, yellow } from "colors";
-import { get } from "request-promise";
-import { ExitHandler } from "./applied_session_agent";
-import { Monitor } from "./monitor";
-import IPCMessageReceiver from "./process_message_router";
-import { ErrorLike, manage } from "./promisified_ipc_manager";
+import cluster from 'cluster';
+import { green, red, white, yellow } from 'colors';
+import { get } from 'request-promise';
+import { ExitHandler } from './applied_session_agent';
+import { Monitor } from './monitor';
+import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, manage } from './promisified_ipc_manager';
/**
* Effectively, each worker repairs the connection to the server by reintroducing a consistent state
@@ -23,18 +23,17 @@ export class ServerWorker extends IPCMessageReceiver {
private isInitialized = false;
public static Create(work: Function) {
if (cluster.isPrimary) {
- console.error(red("cannot create a worker on the monitor process."));
+ console.error(red('cannot create a worker on the monitor process.'));
process.exit(1);
} else if (++ServerWorker.count > 1) {
- ServerWorker.IPCManager.emit("kill", {
- reason: "cannot create more than one worker on a given worker process.",
+ ServerWorker.IPCManager.emit('kill', {
+ reason: 'cannot create more than one worker on a given worker process.',
graceful: false,
- errorCode: 1
+ errorCode: 1,
});
process.exit(1);
- } else {
- return new ServerWorker(work);
}
+ return new ServerWorker(work);
}
/**
@@ -48,7 +47,7 @@ export class ServerWorker extends IPCMessageReceiver {
* server worker (child process). This will also kill
* this process (child process).
*/
- public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>("kill", { reason, graceful, errorCode });
+ public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>('kill', { reason, graceful, errorCode });
/**
* A convenience wrapper to tell the session monitor (parent process)
@@ -60,7 +59,7 @@ export class ServerWorker extends IPCMessageReceiver {
super();
this.configureInternalHandlers();
ServerWorker.IPCManager = manage(process, this.handlers);
- this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`));
+ this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(' ')}]`)}`));
const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
this.serverPort = Number(serverPort);
@@ -78,8 +77,10 @@ export class ServerWorker extends IPCMessageReceiver {
*/
protected configureInternalHandlers = () => {
// updates the local values of variables to the those sent from master
- this.on("updatePollingInterval", ({ newPollingIntervalSeconds }) => this.pollingIntervalSeconds = newPollingIntervalSeconds);
- this.on("manualExit", async ({ isSessionEnd }) => {
+ this.on('updatePollingInterval', ({ newPollingIntervalSeconds }) => {
+ this.pollingIntervalSeconds = newPollingIntervalSeconds;
+ });
+ this.on('manualExit', async ({ isSessionEnd }) => {
await ServerWorker.IPCManager.destroy();
await this.executeExitHandlers(isSessionEnd);
process.exit(0);
@@ -91,7 +92,7 @@ export class ServerWorker extends IPCMessageReceiver {
const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`);
this.proactiveUnplannedExit(appropriateError);
});
- }
+ };
/**
* Execute the list of functions registered to be called
@@ -102,7 +103,7 @@ export class ServerWorker extends IPCMessageReceiver {
/**
* Notify master thread (which will log update in the console) of initialization via IPC.
*/
- public lifecycleNotification = (event: string) => this.emit("lifecycle", { event });
+ public lifecycleNotification = (event: string) => this.emit('lifecycle', { event });
/**
* Called whenever the process has a reason to terminate, either through an uncaught exception
@@ -120,11 +121,11 @@ export class ServerWorker extends IPCMessageReceiver {
this.lifecycleNotification(red(error.message));
await ServerWorker.IPCManager.destroy();
process.exit(1);
- }
+ };
/**
* This monitors the health of the server by submitting a get request to whatever port / route specified
- * by the configuration every n seconds, where n is also given by the configuration.
+ * by the configuration every n seconds, where n is also given by the configuration.
*/
private pollServer = async (): Promise<void> => {
await new Promise<void>(resolve => {
@@ -156,6 +157,5 @@ export class ServerWorker extends IPCMessageReceiver {
});
// controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed
this.pollServer();
- }
-
+ };
}
diff --git a/src/server/DashSession/Session/utilities/repl.ts b/src/server/DashSession/Session/utilities/repl.ts
index 643141286..5d9f15e4c 100644
--- a/src/server/DashSession/Session/utilities/repl.ts
+++ b/src/server/DashSession/Session/utilities/repl.ts
@@ -1,5 +1,5 @@
-import { createInterface, Interface } from "readline";
-import { red, green, white } from "colors";
+import { createInterface, Interface } from 'readline';
+import { red, green, white } from 'colors';
export interface Configuration {
identifier: () => string | string;
@@ -32,76 +32,82 @@ export default class Repl {
this.interface = createInterface(process.stdin, process.stdout).on('line', this.considerInput);
}
- private resolvedIdentifier = () => typeof this.identifier === "string" ? this.identifier : this.identifier();
+ private resolvedIdentifier = () => (typeof this.identifier === 'string' ? this.identifier : this.identifier());
private usage = (command: string, validCommand: boolean) => {
if (validCommand) {
const formatted = white(command);
- const patterns = green(this.commandMap.get(command)!.map(({ argPatterns }) => `${formatted} ${argPatterns.join(" ")}`).join('\n'));
+ const patterns = green(
+ this.commandMap
+ .get(command)!
+ .map(({ argPatterns }) => `${formatted} ${argPatterns.join(' ')}`)
+ .join('\n')
+ );
return `${this.resolvedIdentifier()}\nthe given arguments do not match any registered patterns for ${formatted}\nthe list of valid argument patterns is given by:\n${patterns}`;
- } else {
- const resolved = this.keys;
- if (resolved) {
- return resolved;
- }
- const members: string[] = [];
- const keys = this.commandMap.keys();
- let next: IteratorResult<string>;
- while (!(next = keys.next()).done) {
- members.push(next.value);
- }
- return `${this.resolvedIdentifier()} commands: { ${members.sort().join(", ")} }`;
}
- }
+ const resolved = this.keys;
+ if (resolved) {
+ return resolved;
+ }
+ const members: string[] = [];
+ const keys = this.commandMap.keys();
+ let next: IteratorResult<string>;
+ // eslint-disable-next-line no-cond-assign
+ while (!(next = keys.next()).done) {
+ members.push(next.value);
+ }
+ return `${this.resolvedIdentifier()} commands: { ${members.sort().join(', ')} }`;
+ };
private success = (command: string) => `${this.resolvedIdentifier()} completed local execution of ${white(command)}`;
public registerCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => {
const existing = this.commandMap.get(basename);
- const converted = argPatterns.map(input => input instanceof RegExp ? input : new RegExp(input));
+ const converted = argPatterns.map(input => (input instanceof RegExp ? input : new RegExp(input)));
const registration = { argPatterns: converted, action };
if (existing) {
existing.push(registration);
} else {
this.commandMap.set(basename, [registration]);
}
- }
+ };
private invalid = (command: string, validCommand: boolean) => {
- console.log(red(typeof this.onInvalid === "string" ? this.onInvalid : this.onInvalid(command, validCommand)));
+ console.log(red(typeof this.onInvalid === 'string' ? this.onInvalid : this.onInvalid(command, validCommand)));
this.busy = false;
- }
+ };
private valid = (command: string) => {
- console.log(green(typeof this.onValid === "string" ? this.onValid : this.onValid(command)));
+ console.log(green(typeof this.onValid === 'string' ? this.onValid : this.onValid(command)));
this.busy = false;
- }
+ };
- private considerInput = async (line: string) => {
+ private considerInput = async (lineIn: string) => {
if (this.busy) {
- console.log(red("Busy"));
+ console.log(red('Busy'));
return;
}
this.busy = true;
- line = line.trim();
+ let line = lineIn.trim();
if (this.isCaseSensitive) {
line = line.toLowerCase();
}
const [command, ...args] = line.split(/\s+/g);
if (!command) {
- return this.invalid(command, false);
+ this.invalid(command, false);
+ return;
}
const registered = this.commandMap.get(command);
if (registered) {
const { length } = args;
const candidates = registered.filter(({ argPatterns: { length: count } }) => count === length);
- for (const { argPatterns, action } of candidates) {
+ candidates.forEach(({ argPatterns, action }: { argPatterns: any; action: any }) => {
const parsed: string[] = [];
let matched = true;
if (length) {
for (let i = 0; i < length; i++) {
- let matches: RegExpExecArray | null;
- if ((matches = argPatterns[i].exec(args[i])) === null) {
+ const matches = argPatterns[i].exec(args[i]);
+ if (matches === null) {
matched = false;
break;
}
@@ -110,19 +116,17 @@ export default class Repl {
}
if (!length || matched) {
const result = action(parsed);
- const resolve = () => this.valid(`${command} ${parsed.join(" ")}`);
+ const resolve = () => this.valid(`${command} ${parsed.join(' ')}`);
if (result instanceof Promise) {
result.then(resolve);
} else {
resolve();
}
- return;
}
- }
+ });
this.invalid(command, true);
} else {
this.invalid(command, false);
}
- }
-
-} \ No newline at end of file
+ };
+}
diff --git a/src/server/DashSession/Session/utilities/session_config.ts b/src/server/DashSession/Session/utilities/session_config.ts
index 266759929..b42c1a3c7 100644
--- a/src/server/DashSession/Session/utilities/session_config.ts
+++ b/src/server/DashSession/Session/utilities/session_config.ts
@@ -1,85 +1,85 @@
-import { Schema } from "jsonschema";
-import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from "colors";
+import { Schema } from 'jsonschema';
+import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from 'colors';
const colorPattern = /black|red|green|yellow|blue|magenta|cyan|white|gray|grey/;
const identifierProperties: Schema = {
- type: "object",
+ type: 'object',
properties: {
text: {
- type: "string",
- minLength: 1
+ type: 'string',
+ minLength: 1,
},
color: {
- type: "string",
- pattern: colorPattern
- }
- }
+ type: 'string',
+ pattern: colorPattern,
+ },
+ },
};
const portProperties: Schema = {
- type: "number",
+ type: 'number',
minimum: 443,
- maximum: 65535
+ maximum: 65535,
};
export const configurationSchema: Schema = {
- id: "/configuration",
- type: "object",
+ id: '/configuration',
+ type: 'object',
properties: {
- showServerOutput: { type: "boolean" },
+ showServerOutput: { type: 'boolean' },
ports: {
- type: "object",
+ type: 'object',
properties: {
server: portProperties,
- socket: portProperties
+ socket: portProperties,
},
- required: ["server"],
- additionalProperties: true
+ required: ['server'],
+ additionalProperties: true,
},
identifiers: {
- type: "object",
+ type: 'object',
properties: {
master: identifierProperties,
worker: identifierProperties,
- exec: identifierProperties
- }
+ exec: identifierProperties,
+ },
},
polling: {
- type: "object",
+ type: 'object',
additionalProperties: false,
properties: {
intervalSeconds: {
- type: "number",
+ type: 'number',
minimum: 1,
- maximum: 86400
+ maximum: 86400,
},
route: {
- type: "string",
- pattern: /\/[a-zA-Z]*/g
+ type: 'string',
+ pattern: /\/[a-zA-Z]*/g,
},
failureTolerance: {
- type: "number",
+ type: 'number',
minimum: 0,
- }
- }
+ },
+ },
},
- }
+ },
};
-type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
+type ColorLabel = 'yellow' | 'red' | 'cyan' | 'green' | 'blue' | 'magenta' | 'grey' | 'gray' | 'white' | 'black';
export const colorMapping: Map<ColorLabel, Color> = new Map([
- ["yellow", yellow],
- ["red", red],
- ["cyan", cyan],
- ["green", green],
- ["blue", blue],
- ["magenta", magenta],
- ["grey", grey],
- ["gray", gray],
- ["white", white],
- ["black", black]
+ ['yellow', yellow],
+ ['red', red],
+ ['cyan', cyan],
+ ['green', green],
+ ['blue', blue],
+ ['magenta', magenta],
+ ['grey', grey],
+ ['gray', gray],
+ ['white', white],
+ ['black', black],
]);
interface Identifier {
@@ -108,22 +108,22 @@ export const defaultConfig: Configuration = {
showServerOutput: false,
identifiers: {
master: {
- text: "__monitor__",
- color: "yellow"
+ text: '__monitor__',
+ color: 'yellow',
},
worker: {
- text: "__server__",
- color: "magenta"
+ text: '__server__',
+ color: 'magenta',
},
exec: {
- text: "__exec__",
- color: "green"
- }
+ text: '__exec__',
+ color: 'green',
+ },
},
ports: { server: 1050 },
polling: {
- route: "/",
+ route: '/',
intervalSeconds: 30,
- failureTolerance: 0
- }
-}; \ No newline at end of file
+ failureTolerance: 0,
+ },
+};
diff --git a/src/server/DashSession/Session/utilities/utilities.ts b/src/server/DashSession/Session/utilities/utilities.ts
index eb8de9d7e..a2ba29c67 100644
--- a/src/server/DashSession/Session/utilities/utilities.ts
+++ b/src/server/DashSession/Session/utilities/utilities.ts
@@ -1,31 +1,16 @@
-import { v4 } from "uuid";
+import { v4 } from 'uuid';
export namespace Utilities {
-
export function guid() {
return v4();
}
- /**
- * At any arbitrary layer of nesting within the configuration objects, any single value that
- * is not specified by the configuration is given the default counterpart. If, within an object,
- * one peer is given by configuration and two are not, the one is preserved while the two are given
- * the default value.
- * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
- * granularity in the overwriting of nested objects
- */
- export function preciseAssign(target: any, ...sources: any[]): any {
- for (const source of sources) {
- preciseAssignHelper(target, source);
- }
- return target;
- }
-
export function preciseAssignHelper(target: any, source: any) {
- Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => {
- let targetValue: any, sourceValue: any;
- if (sourceValue = source[property]) {
- if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") {
+ Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).forEach(property => {
+ const targetValue = target[property];
+ const sourceValue = source[property];
+ if (sourceValue) {
+ if (typeof sourceValue === 'object' && typeof targetValue === 'object') {
preciseAssignHelper(targetValue, sourceValue);
} else {
target[property] = sourceValue;
@@ -34,4 +19,18 @@ export namespace Utilities {
});
}
-} \ No newline at end of file
+ /**
+ * At any arbitrary layer of nesting within the configuration objects, any single value that
+ * is not specified by the configuration is given the default counterpart. If, within an object,
+ * one peer is given by configuration and two are not, the one is preserved while the two are given
+ * the default value.
+ * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
+ * granularity in the overwriting of nested objects
+ */
+ export function preciseAssign(target: any, ...sources: any[]): any {
+ sources.forEach(source => {
+ preciseAssignHelper(target, source);
+ });
+ return target;
+ }
+}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index a9e6af67c..808d2c6f2 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -1,9 +1,7 @@
import { cyan, magenta } from 'colors';
import { Response } from 'express';
-import SocketIO from 'socket.io';
-import { timeMap } from './ApiManagers/UserManager';
-import { WebSocket } from './websocket';
import * as fs from 'fs';
+import { socketMap, timeMap, userOperations } from './SocketData';
/**
* DashStats focuses on tracking user data for each session.
@@ -17,7 +15,6 @@ export namespace DashStats {
const statsCSVDirectory = './src/server/stats/';
const statsCSVFilename = statsCSVDirectory + 'userLoginStats.csv';
- const columns = ['USERNAME', 'ACTION', 'TIME'];
/**
* UserStats holds the stats associated with a particular user.
@@ -78,111 +75,14 @@ export namespace DashStats {
export const lastUserOperations = new Map<string, UserLastOperations>();
/**
- * handleStats is called when the /stats route is called, providing a JSON
- * object with relevant stats. In this case, we return the number of
- * current connections and
- * @param res Response object from Express
- */
- export function handleStats(res: Response) {
- let current = getCurrentStats();
- const results: CSVStore[] = [];
- res.json({
- currentConnections: current.length,
- socketMap: current,
- });
- }
-
- /**
- * getUpdatedStatesBundle() sends an updated copy of the current stats to the
- * frontend /statsview route via websockets.
- *
- * @returns a StatsDataBundle that is sent to the frontend view on each websocket update
- */
- export function getUpdatedStatsBundle(): StatsDataBundle {
- let current = getCurrentStats();
-
- return {
- connectedUsers: current,
- };
- }
-
- /**
- * handleStatsView() is called when the /statsview route is called. This
- * will use pug to render a frontend view of the current stats
- *
- * @param res
- */
- export function handleStatsView(res: Response) {
- let current = getCurrentStats();
-
- let connectedUsers = current.map(socketPair => {
- return socketPair.time + ' - ' + socketPair.username + ' Operations: ' + socketPair.operations;
- });
-
- let serverTraffic = ServerTraffic.NOT_BUSY;
- if (current.length < BUSY_SERVER_BOUND) {
- serverTraffic = ServerTraffic.NOT_BUSY;
- } else if (current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) {
- serverTraffic = ServerTraffic.BUSY;
- } else {
- serverTraffic = ServerTraffic.VERY_BUSY;
- }
-
- res.render('stats.pug', {
- title: 'Dash Stats',
- numConnections: connectedUsers.length,
- serverTraffic: serverTraffic,
- serverTrafficMessage: serverTrafficMessages[serverTraffic],
- connectedUsers: connectedUsers,
- });
- }
-
- /**
- * logUserLogin() writes a login event to the CSV file.
- *
- * @param username the username in the format of "username@domain.com logged in"
- * @param socket the websocket associated with the current connection
- */
- export function logUserLogin(username: string | undefined, socket: SocketIO.Socket) {
- if (!(username === undefined)) {
- let currentDate = new Date();
- console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`));
-
- let toWrite: CSVStore = {
- USERNAME: username,
- ACTION: 'loggedIn',
- TIME: currentDate.toISOString(),
- };
-
- if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory);
- let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
- statsFile.write(convertToCSV(toWrite));
- statsFile.end();
- console.log(cyan(convertToCSV(toWrite)));
- }
- }
-
- /**
- * logUserLogout() writes a logout event to the CSV file.
- *
- * @param username the username in the format of "username@domain.com logged in"
- * @param socket the websocket associated with the current connection.
+ * convertToCSV() is a helper method that stringifies a CSVStore object
+ * that can be written to the CSV file later.
+ * @param dataObject the object to stringify
+ * @returns the object as a string.
*/
- export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) {
- if (!(username === undefined)) {
- let currentDate = new Date();
-
- let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
- let toWrite: CSVStore = {
- USERNAME: username,
- ACTION: 'loggedOut',
- TIME: currentDate.toISOString(),
- };
- statsFile.write(convertToCSV(toWrite));
- statsFile.end();
- }
+ function convertToCSV(dataObject: CSVStore): string {
+ return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`;
}
-
/**
* getLastOperationsOrDefault() is a helper method that will attempt
* to query the lastUserOperations map for a specified username. If the
@@ -193,7 +93,7 @@ export namespace DashStats {
*/
function getLastOperationsOrDefault(username: string): UserLastOperations {
if (lastUserOperations.get(username) === undefined) {
- let initializeOperationsQueue = [];
+ const initializeOperationsQueue = [];
for (let i = 0; i < RATE_INTERVAL; i++) {
initializeOperationsQueue.push(0);
}
@@ -217,7 +117,7 @@ export namespace DashStats {
*/
function updateLastOperations(lastOperationData: UserLastOperations, currentOperations: number): UserLastOperations {
// create a copy of the UserLastOperations to modify
- let newLastOperationData: UserLastOperations = {
+ const newLastOperationData: UserLastOperations = {
sampleOperations: lastOperationData.sampleOperations,
lastSampleOperations: lastOperationData.lastSampleOperations,
previousOperationsQueue: lastOperationData.previousOperationsQueue.slice(),
@@ -225,7 +125,7 @@ export namespace DashStats {
let newSampleOperations = newLastOperationData.sampleOperations;
newSampleOperations -= newLastOperationData.previousOperationsQueue.shift()!; // removes and returns the first element of the queue
- let operationsThisCycle = currentOperations - lastOperationData.lastSampleOperations;
+ const operationsThisCycle = currentOperations - lastOperationData.lastSampleOperations;
newSampleOperations += operationsThisCycle; // add the operations this cycle to find out what our count for the interval should be (e.g operations in the last 10 seconds)
// update values for the copy object
@@ -245,7 +145,7 @@ export namespace DashStats {
* @returns the total number of operations recorded up to this sampling cycle.
*/
function getUserOperationsOrDefault(username: string): number {
- return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)!;
+ return userOperations.get(username) === undefined ? 0 : userOperations.get(username)!;
}
/**
@@ -255,37 +155,129 @@ export namespace DashStats {
* @returns an array of UserStats storing data for each user at the current moment.
*/
function getCurrentStats(): UserStats[] {
- let socketPairs: UserStats[] = [];
- for (let [key, value] of WebSocket.socketMap) {
- let username = value.split(' ')[0];
- let connectionTime = new Date(timeMap[username]);
+ const socketPairs: UserStats[] = [];
+ Array.from(socketMap.entries()).forEach(([key, value]) => {
+ const username = value.split(' ')[0];
+ const connectionTime = new Date(timeMap[username]);
- let connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString();
+ const connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString();
if (!key.disconnected) {
- let lastRecordedOperations = getLastOperationsOrDefault(username);
- let currentUserOperationCount = getUserOperationsOrDefault(username);
+ const lastRecordedOperations = getLastOperationsOrDefault(username);
+ const currentUserOperationCount = getUserOperationsOrDefault(username);
socketPairs.push({
socketId: key.id,
username: username,
time: connectionTimeString.includes('Invalid Date') ? '' : connectionTimeString,
- operations: WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0,
+ operations: userOperations.get(username) ? userOperations.get(username)! : 0,
rate: lastRecordedOperations.sampleOperations,
});
lastUserOperations.set(username, updateLastOperations(lastRecordedOperations, currentUserOperationCount));
}
- }
+ });
return socketPairs;
}
/**
- * convertToCSV() is a helper method that stringifies a CSVStore object
- * that can be written to the CSV file later.
- * @param dataObject the object to stringify
- * @returns the object as a string.
+ * handleStats is called when the /stats route is called, providing a JSON
+ * object with relevant stats. In this case, we return the number of
+ * current connections and
+ * @param res Response object from Express
*/
- function convertToCSV(dataObject: CSVStore): string {
- return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`;
+ export function handleStats(res: Response) {
+ const current = getCurrentStats();
+ res.json({
+ currentConnections: current.length,
+ socketMap: current,
+ });
+ }
+
+ /**
+ * getUpdatedStatesBundle() sends an updated copy of the current stats to the
+ * frontend /statsview route via websockets.
+ *
+ * @returns a StatsDataBundle that is sent to the frontend view on each websocket update
+ */
+ export function getUpdatedStatsBundle(): StatsDataBundle {
+ const current = getCurrentStats();
+
+ return {
+ connectedUsers: current,
+ };
+ }
+
+ /**
+ * handleStatsView() is called when the /statsview route is called. This
+ * will use pug to render a frontend view of the current stats
+ *
+ * @param res
+ */
+ export function handleStatsView(res: Response) {
+ const current = getCurrentStats();
+ const connectedUsers = current.map(({ time, username, operations }) => time + ' - ' + username + ' Operations: ' + operations);
+
+ let serverTraffic = ServerTraffic.NOT_BUSY;
+ if (current.length < BUSY_SERVER_BOUND) {
+ serverTraffic = ServerTraffic.NOT_BUSY;
+ } else if (current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) {
+ serverTraffic = ServerTraffic.BUSY;
+ } else {
+ serverTraffic = ServerTraffic.VERY_BUSY;
+ }
+
+ res.render('stats.pug', {
+ title: 'Dash Stats',
+ numConnections: connectedUsers.length,
+ serverTraffic: serverTraffic,
+ serverTrafficMessage: serverTrafficMessages[serverTraffic],
+ connectedUsers: connectedUsers,
+ });
+ }
+
+ /**
+ * logUserLogin() writes a login event to the CSV file.
+ *
+ * @param username the username in the format of "username@domain.com logged in"
+ * @param socket the websocket associated with the current connection
+ */
+ export function logUserLogin(username: string | undefined) {
+ if (!(username === undefined)) {
+ const currentDate = new Date();
+ console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`));
+
+ const toWrite: CSVStore = {
+ USERNAME: username,
+ ACTION: 'loggedIn',
+ TIME: currentDate.toISOString(),
+ };
+
+ if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory);
+ const statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
+ statsFile.write(convertToCSV(toWrite));
+ statsFile.end();
+ console.log(cyan(convertToCSV(toWrite)));
+ }
+ }
+
+ /**
+ * logUserLogout() writes a logout event to the CSV file.
+ *
+ * @param username the username in the format of "username@domain.com logged in"
+ * @param socket the websocket associated with the current connection.
+ */
+ export function logUserLogout(username: string | undefined) {
+ if (!(username === undefined)) {
+ const currentDate = new Date();
+
+ const statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
+ const toWrite: CSVStore = {
+ USERNAME: username,
+ ACTION: 'loggedOut',
+ TIME: currentDate.toISOString(),
+ };
+ statsFile.write(convertToCSV(toWrite));
+ statsFile.end();
+ }
}
}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 307aec6fc..08cea1de5 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -15,16 +15,15 @@ import { basename } from 'path';
import * as parse from 'pdf-parse';
import * as request from 'request-promise';
import { Duplex, Stream } from 'stream';
-import { filesDirectory, publicDirectory } from '.';
import { Utils } from '../Utils';
-import { Opt } from '../fields/Doc';
-import { ParsedPDF } from '../server/PdfTypes';
import { createIfNotExists } from './ActionUtilities';
import { AzureManager } from './ApiManagers/AzureManager';
-import { Directory, clientPathToFile, pathToDirectory, serverPathToFile } from './ApiManagers/UploadManager';
+import { ParsedPDF } from './PdfTypes';
import { AcceptableMedia, Upload } from './SharedMediaTypes';
+import { Directory, clientPathToFile, filesDirectory, pathToDirectory, publicDirectory, serverPathToFile } from './SocketData';
import { resolvedServerUrl } from './server_Initialization';
-const spawn = require('child_process').spawn;
+
+const { spawn } = require('child_process');
const { exec } = require('child_process');
const requestImageSize = require('../client/util/request-image-size');
@@ -42,7 +41,7 @@ export function InjectSize(filename: string, size: SizeSuffix) {
}
function isLocal() {
- return /Dash-Web[0-9]*[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/;
+ return /Dash-Web[0-9]*[\\/]src[\\/]server[\\/]public[\\/](.*)/;
}
function usingAzure() {
@@ -68,11 +67,21 @@ 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 { BLOBSTORE_URL, RESIZE_FUNCTION_URL } = process.env;
- const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr
+ const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; // TODO:glr
+ export function fExists(name: string, destination: Directory) {
+ const destinationPath = serverPathToFile(destination, name);
+ return existsSync(destinationPath);
+ }
+
+ export function getAccessPaths(directory: Directory, fileName: string) {
+ return {
+ client: clientPathToFile(directory, fileName),
+ server: serverPathToFile(directory, fileName),
+ };
+ }
export async function concatVideos(filePaths: string[]): Promise<Upload.AccessPathInfo> {
// make a list of paths to create the ordered text file for ffmpeg
const inputListName = 'concat.txt';
@@ -80,14 +89,14 @@ export namespace DashUploadUtils {
// make a list of paths to create the ordered text file for ffmpeg
const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n');
// write the text file to the file system
- await new Promise<void>((res, reject) =>
+ await new Promise<void>((res, reject) => {
writeFile(textFilePath, filePathsText, err => {
if (err) {
reject();
console.log(err);
} else res();
- })
- );
+ });
+ });
// make output file name based on timestamp
const outputFileName = `output-${Utils.GenerateGuid()}.mp4`;
@@ -95,19 +104,19 @@ export namespace DashUploadUtils {
const outputFilePath = path.join(pathToDirectory(Directory.videos), outputFileName);
// concatenate the videos
- await new Promise((resolve, reject) =>
+ await new Promise((resolve, reject) => {
ffmpeg()
.input(textFilePath)
.inputOptions(['-f concat', '-safe 0'])
// .outputOptions('-c copy')
- //.videoCodec("copy")
+ // .videoCodec("copy")
.save(outputFilePath)
.on('error', (err: any) => {
console.log(err);
reject();
})
- .on('end', resolve)
- );
+ .on('end', resolve);
+ });
// delete concat.txt from the file system
unlinkSync(textFilePath);
@@ -135,270 +144,76 @@ export namespace DashUploadUtils {
};
}
- export function QueryYoutubeProgress(videoId: string, user?: Express.User) {
+ export const uploadProgress = new Map<string, string>();
+
+ export function QueryYoutubeProgress(videoId: string) {
// console.log(`PROGRESS:${videoId}`, (user as any)?.email);
return uploadProgress.get(videoId) ?? 'pending data upload';
}
- export let uploadProgress = new Map<string, string>();
-
- export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> {
- return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => {
- const name = videoId;
- const filepath = name.replace(/^-/, '__') + '.mp4';
- const finalPath = serverPathToFile(Directory.videos, filepath);
- if (existsSync(finalPath)) {
- uploadProgress.set(overwriteId, 'computing duration');
- exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
- const time = Array.from(stdout.trim().split(':')).reverse();
- const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
- res(resolveExistingFile(name, filepath, Directory.videos, 'video/mp4', duration, undefined));
- });
- } else {
- uploadProgress.set(overwriteId, 'starting download');
- const ytdlp = spawn(`yt-dlp`, ['-o', filepath, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']);
-
- ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString()));
-
- let errors = '';
- ytdlp.stderr.on('data', (data: any) => {
- uploadProgress.set(overwriteId, 'error:' + data.toString());
- errors = data.toString();
- });
-
- ytdlp.on('exit', function (code: any) {
- if (code) {
- res({
- source: {
- size: 0,
- filepath: name,
- originalFilename: name,
- newFilename: name,
- mimetype: 'video',
- hashAlgorithm: 'md5',
- toJSON: () => ({ newFilename: name, filepath, mimetype: 'video', mtime: new Date(), size: 0, length: 0, originalFilename: name }),
- },
- result: { name: 'failed youtube query', message: `Could not archive video. ${code ? errors : uploadProgress.get(videoId)}` },
- });
- } else {
- uploadProgress.set(overwriteId, 'computing duration');
- exec(`yt-dlp-o ${filepath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
- const time = Array.from(stdout.trim().split(':')).reverse();
- const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
- const data = { size: 0, filepath, name, mimetype: 'video', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' };
- const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), toJson: () => undefined as any }) };
- MoveParsedFile(file, Directory.videos).then(output => {
- console.log('OUTPUT = ' + output);
- res(output);
- });
- });
- }
+ /**
+ * Basically just a wrapper around rename, which 'deletes'
+ * the file at the old path and 'moves' it to the new one. For simplicity, the
+ * caller just has to pass in the name of the target directory, and this function
+ * will resolve the actual target path from that.
+ * @param file The file to move
+ * @param destination One of the specific media asset directories into which to move it
+ * @param suffix If the file doesn't have a suffix and you want to provide it one
+ * to appear in the new location
+ */
+ export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix?: string, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> {
+ const { filepath } = file;
+ let name = targetName ?? path.basename(filepath);
+ suffix && (name += suffix);
+ return new Promise(resolve => {
+ const destinationPath = serverPathToFile(destination, name);
+ rename(filepath, destinationPath, error => {
+ resolve({
+ source: file,
+ result: error ?? {
+ accessPaths: {
+ agnostic: getAccessPaths(destination, name),
+ },
+ rawText: text,
+ duration,
+ },
});
- }
+ });
});
}
- export async function upload(file: File, overwriteGuid?: string): Promise<Upload.FileResponse> {
- const isAzureOn = usingAzure();
- const { mimetype: type, filepath, originalFilename } = 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.
-
- const category = types[0];
- let format = `.${types[1]}`;
- console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${type}) in category (${category}).`));
-
- switch (category) {
- case 'image':
- if (imageFormats.includes(format)) {
- const result = await UploadImage(filepath, basename(filepath));
- return { source: file, result };
- }
- fs.unlink(filepath, () => {});
- return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .jpg` } };
- case 'video':
- if (format.includes('x-matroska')) {
- console.log('case video');
- await new Promise(res =>
- ffmpeg(file.filepath)
- .videoCodec('copy') // this will copy the data instead of reencode it
- .save(file.filepath.replace('.mkv', '.mp4'))
- .on('end', res)
- .on('error', (e: any) => console.log(e))
- );
- file.filepath = file.filepath.replace('.mkv', '.mp4');
- format = '.mp4';
- }
- if (format.includes('quicktime')) {
- let abort = false;
- await new Promise<void>(res =>
- ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => {
- if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) {
- abort = true;
- }
- res();
- })
- );
- if (abort) {
- // bcz: instead of aborting, we could convert the file using the code below to an mp4. Problem is that this takes a long time and will clog up the server.
- // await new Promise(res =>
- // ffmpeg(file.path)
- // .videoCodec('libx264') // this will copy the data instead of reencode it
- // .audioCodec('mp2')
- // .save(file.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
- // .on('end', res)
- // );
- // file.path = file.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
- // format = '.mp4';
- fs.unlink(filepath, () => {});
- return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
- }
- }
- if (videoFormats.includes(format) || format.includes('.webm')) {
- return MoveParsedFile(file, Directory.videos);
- }
- fs.unlink(filepath, () => {});
- return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
- case 'application':
- if (applicationFormats.includes(format)) {
- const val = UploadPdf(file);
- if (val) return val;
- }
- case 'audio':
- const components = format.split(';');
- if (components.length > 1) {
- format = components[0];
- }
- if (audioFormats.includes(format)) {
- return UploadAudio(file, format);
- }
- fs.unlink(filepath, () => {});
- return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp3` } };
- case 'text':
- if (types[1] == 'csv') {
- return UploadCsv(file);
- }
- }
-
- console.log(red(`Ignoring unsupported file (${originalFilename}) with upload type (${type}).`));
- fs.unlink(filepath, () => {});
- return { source: file, result: new Error(`Could not upload unsupported file (${originalFilename}) with upload type (${type}).`) };
- }
-
- async function UploadPdf(file: File) {
- const fileKey = (await md5File(file.filepath)) + '.pdf';
- const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
- if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
- fs.unlink(file.filepath, () => {});
- return new Promise<Upload.FileResponse>(res => {
- const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
- const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
- var rawText = '';
- readStream
- .on('data', chunk => (rawText += chunk.toString())) //
- .on('end', () => res(resolveExistingFile(file.originalFilename ?? '', fileKey, Directory.pdfs, file.mimetype, undefined, rawText)));
- });
- }
- const dataBuffer = readFileSync(file.filepath);
- const result: ParsedPDF | any = await parse(dataBuffer).catch((e: any) => e);
- if (!result.code) {
- await new Promise<void>((resolve, reject) => {
- const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
- writeStream.write(result?.text, error => (error ? reject(error) : resolve()));
+ const parseExifData = async (source: string) => {
+ const image = await request.get(source, { encoding: null });
+ const { /* data, */ error } = await new Promise<{ data: any; error: any }>(resolve => {
+ // eslint-disable-next-line no-new
+ new ExifImage({ image }, (exifError, data) => {
+ const reason = (exifError as any)?.code;
+ resolve({ data, error: reason });
});
- return MoveParsedFile(file, Directory.pdfs, undefined, result?.text, undefined, fileKey);
- }
- return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.originalFilename}).${result.message}` } };
- }
-
- async function UploadCsv(file: File) {
- const { filepath: sourcePath } = file;
- // read the file as a string
- const data = readFileSync(sourcePath, 'utf8');
- // split the string into an array of lines
- return MoveParsedFile(file, Directory.csv, undefined, data);
- // console.log(csvParser(data));
- }
-
- const manualSuffixes = ['.webm'];
-
- async function UploadAudio(file: File, format: string) {
- const suffix = manualSuffixes.includes(format) ? format : undefined;
- return MoveParsedFile(file, Directory.audio, suffix);
- }
-
- /**
- * Uploads an image specified by the @param source to Dash's /public/files/
- * directory, and returns information generated during that upload
- *
- * @param {string} source is either the absolute path of an already uploaded image or
- * the url of a remote image
- * @param {string} filename dictates what to call the image. If not specified,
- * the name {@param prefix}_upload_{GUID}
- * @param {string} prefix is a string prepended to the generated image name in the
- * event that @param filename is not specified
- *
- * @returns {ImageUploadInformation | Error} This method returns
- * 1) the paths to the uploaded images (plural due to resizing)
- * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed
- * 3) the size of the image, in bytes (4432130)
- * 4) the content type of the image, i.e. image/(jpeg | png | ...)
- */
- export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
- const metadata = await InspectImage(source);
- if (metadata instanceof Error) {
- return { name: metadata.name, message: metadata.message };
- }
- const outputFile = filename || metadata.filename || '';
-
- return UploadInspectedImage(metadata, outputFile, prefix);
+ });
+ return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
};
-
- export async function buildFileDirectories() {
- if (!existsSync(publicDirectory)) {
- console.error('\nPlease ensure that the following directory exists...\n');
- console.log(publicDirectory);
- process.exit(0);
- }
- if (!existsSync(filesDirectory)) {
- console.error('\nPlease ensure that the following directory exists...\n');
- console.log(filesDirectory);
- process.exit(0);
- }
- const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`));
- return Promise.all(pending);
- }
-
- export interface RequestedImageSize {
- width: number;
- height: number;
- type: string;
- }
-
- export interface ImageResizer {
- width: number;
- suffix: SizeSuffix;
- }
-
/**
* Based on the url's classification as local or remote, gleans
* as much information as possible about the specified image
*
* @param source is the path or url to the image in question
*/
- export const InspectImage = async (source: string): Promise<Upload.InspectionResults | Error> => {
- let rawMatches: RegExpExecArray | null;
+ export const InspectImage = async (sourceIn: string): Promise<Upload.InspectionResults | Error> => {
+ let source = sourceIn;
+ const rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source);
let filename: string | undefined;
/**
* Just more edge case handling: this if clause handles the case where an image onto the canvas that
* is represented by a base64 encoded data uri, rather than a proper file. We manually write it out
* to the server and then carry on as if it had been put there by the Formidable form / file parser.
*/
- if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) {
+ if (rawMatches !== null) {
const [ext, data] = rawMatches.slice(1, 3);
- const resolved = (filename = `upload_${Utils.GenerateGuid()}.${ext}`);
+ filename = `upload_${Utils.GenerateGuid()}.${ext}`;
+ const resolved = filename;
if (usingAzure()) {
- const response = await AzureManager.UploadBase64ImageBlob(resolved, data);
+ await AzureManager.UploadBase64ImageBlob(resolved, data);
source = `${AzureManager.BASE_STRING}/${resolved}`;
} else {
source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
@@ -438,7 +253,7 @@ export namespace DashUploadUtils {
// Use the request library to parse out file level image information in the headers
const { headers } = await new Promise<any>((resolve, reject) => {
- return request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res)));
+ request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res)));
}).catch(e => {
console.log('Error processing headers: ', e);
});
@@ -449,6 +264,7 @@ export namespace DashUploadUtils {
// Bundle up the information into an object
return {
source,
+ // eslint-disable-next-line radix
contentSize: parseInt(headers[size]),
contentType: headers[type],
nativeWidth,
@@ -462,49 +278,71 @@ export namespace DashUploadUtils {
}
};
+ async function correctRotation(imgSourcePath: string) {
+ const buffer = fs.readFileSync(imgSourcePath);
+ try {
+ return (await autorotate.rotate(buffer, { quality: 30 })).buffer;
+ } catch (e) {
+ return buffer;
+ }
+ }
+
/**
- * Basically just a wrapper around rename, which 'deletes'
- * the file at the old path and 'moves' it to the new one. For simplicity, the
- * caller just has to pass in the name of the target directory, and this function
- * will resolve the actual target path from that.
- * @param file The file to move
- * @param destination One of the specific media asset directories into which to move it
- * @param suffix If the file doesn't have a suffix and you want to provide it one
- * to appear in the new location
+ * define the resizers to use
+ * @param ext the extension
+ * @returns an array of resize descriptions
*/
- export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> {
- const { filepath } = file;
- let name = targetName ?? path.basename(filepath);
- suffix && (name += suffix);
- return new Promise(resolve => {
- const destinationPath = serverPathToFile(destination, name);
- rename(filepath, destinationPath, error => {
- resolve({
- source: file,
- result: error
- ? error
- : {
- accessPaths: {
- agnostic: getAccessPaths(destination, name),
- },
- rawText: text,
- duration,
- },
- });
- });
- });
+ export function imageResampleSizes(ext: string): DashUploadUtils.ImageResizer[] {
+ return [
+ { suffix: SizeSuffix.Original, width: 0 },
+ ...[...(AcceptableMedia.imageFormats.includes(ext.toLowerCase()) ? Object.values(DashUploadUtils.Sizes) : [])].map(({ suffix, width }) => ({
+ width,
+ suffix,
+ })),
+ ];
}
- export function fExists(name: string, destination: Directory) {
- const destinationPath = serverPathToFile(destination, name);
- return existsSync(destinationPath);
- }
+ /**
+ * 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 imgSourcePath file path for image being resized
+ * @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(imgSourcePath: string, outputFileName: string, outputDirectory: string) {
+ const writtenFiles: { [suffix: string]: string } = {};
+ const sizes = imageResampleSizes(path.extname(outputFileName));
- export function getAccessPaths(directory: Directory, fileName: string) {
- return {
- client: clientPathToFile(directory, fileName),
- server: serverPathToFile(directory, fileName),
+ const imgBuffer = await correctRotation(imgSourcePath);
+ const imgReadStream = new Duplex();
+ imgReadStream.push(imgBuffer);
+ imgReadStream.push(null);
+ const outputPath = (suffix: SizeSuffix) => {
+ writtenFiles[suffix] = InjectSize(outputFileName, suffix);
+ return path.resolve(outputDirectory, writtenFiles[suffix]);
};
+ await Promise.all(
+ sizes.filter(({ width }) => !width).map(({ suffix }) =>
+ new Promise<void>(res => {
+ imgReadStream.pipe(createWriteStream(outputPath(suffix))).on('close', res);
+ })
+ )); // prettier-ignore
+
+ return Jimp.read(imgBuffer)
+ .then(async (imgIn: any) => {
+ let img = imgIn;
+ await Promise.all( sizes.filter(({ width }) => width).map(({ width, suffix }) => {
+ img = img.resize(width, Jimp.AUTO).write(outputPath(suffix));
+ return img;
+ } )); // prettier-ignore
+ return writtenFiles;
+ })
+ .catch((e: any) => {
+ console.log('ERROR' + e);
+ return writtenFiles;
+ });
}
/**
@@ -555,119 +393,265 @@ export namespace DashUploadUtils {
} catch (e) {
// input is a blob or other, try reading it to create a metadata source file.
const reqSource = request(metadata.source);
- let readStream: Stream = reqSource instanceof Promise ? await reqSource : reqSource;
+ const readStream: Stream = reqSource instanceof Promise ? await reqSource : reqSource;
const readSource = `${prefix}upload_${Utils.GenerateGuid()}.${metadata.contentType.split('/')[1].toLowerCase()}`;
- await new Promise<void>((res, rej) =>
+ await new Promise<void>((res, rej) => {
readStream
.pipe(createWriteStream(readSource))
.on('close', () => res())
- .on('error', () => rej())
- );
+ .on('error', () => rej());
+ });
writtenFiles = await outputResizedImages(readSource, resolved, pathToDirectory(Directory.images));
- fs.unlink(readSource, err => console.log("Couldn't unlink temporary image file:" + readSource));
+ fs.unlink(readSource, err => console.log("Couldn't unlink temporary image file:" + readSource, err));
}
}
- for (const suffix of Object.keys(writtenFiles)) {
+ Array.from(Object.keys(writtenFiles)).forEach(suffix => {
information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]);
- }
+ });
if (isLocal().test(source) && cleanUp) {
unlinkSync(source);
}
return information;
};
- const bufferConverterRec = (layer: any) => {
- for (const key of Object.keys(layer)) {
- const val: any = layer[key];
- if (val instanceof Buffer) {
- layer[key] = val.toString();
- } else if (Array.isArray(val) && typeof val[0] === 'number') {
- layer[key] = Buffer.from(val).toString();
- } else if (typeof val === 'object') {
- bufferConverterRec(val);
- }
+ /**
+ * Uploads an image specified by the @param source to Dash's /public/files/
+ * directory, and returns information generated during that upload
+ *
+ * @param {string} source is either the absolute path of an already uploaded image or
+ * the url of a remote image
+ * @param {string} filename dictates what to call the image. If not specified,
+ * the name {@param prefix}_upload_{GUID}
+ * @param {string} prefix is a string prepended to the generated image name in the
+ * event that @param filename is not specified
+ *
+ * @returns {ImageUploadInformation | Error} This method returns
+ * 1) the paths to the uploaded images (plural due to resizing)
+ * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed
+ * 3) the size of the image, in bytes (4432130)
+ * 4) the content type of the image, i.e. image/(jpeg | png | ...)
+ */
+ export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
+ const metadata = await InspectImage(source);
+ if (metadata instanceof Error) {
+ return { name: metadata.name, message: metadata.message };
}
+ const outputFile = filename || metadata.filename || '';
+
+ return UploadInspectedImage(metadata, outputFile, prefix);
};
- const parseExifData = async (source: string) => {
- const image = await request.get(source, { encoding: null });
- const { data, error } = await new Promise<{ data: any; error: any }>(resolve => {
- new ExifImage({ image }, (error, data) => {
- let reason: Opt<string> = undefined;
- if (error) {
- reason = (error as any).code;
- }
- resolve({ data, error: reason });
- });
+ export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> {
+ return new Promise<Upload.FileResponse<Upload.FileInformation>>(res => {
+ const name = videoId;
+ const filepath = name.replace(/^-/, '__') + '.mp4';
+ const finalPath = serverPathToFile(Directory.videos, filepath);
+ if (existsSync(finalPath)) {
+ uploadProgress.set(overwriteId, 'computing duration');
+ exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any /* , stderr: any */) => {
+ const time = Array.from(stdout.trim().split(':')).reverse();
+ const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+ res(resolveExistingFile(name, filepath, Directory.videos, 'video/mp4', duration, undefined));
+ });
+ } else {
+ uploadProgress.set(overwriteId, 'starting download');
+ const ytdlp = spawn(`yt-dlp`, ['-o', filepath, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']);
+
+ ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString()));
+
+ let errors = '';
+ ytdlp.stderr.on('data', (data: any) => {
+ uploadProgress.set(overwriteId, 'error:' + data.toString());
+ errors = data.toString();
+ });
+
+ ytdlp.on('exit', (code: any) => {
+ if (code) {
+ res({
+ source: {
+ size: 0,
+ filepath: name,
+ originalFilename: name,
+ newFilename: name,
+ mimetype: 'video',
+ hashAlgorithm: 'md5',
+ toJSON: () => ({ newFilename: name, filepath, mimetype: 'video', mtime: new Date(), size: 0, length: 0, originalFilename: name }),
+ },
+ result: { name: 'failed youtube query', message: `Could not archive video. ${code ? errors : uploadProgress.get(videoId)}` },
+ });
+ } else {
+ uploadProgress.set(overwriteId, 'computing duration');
+ exec(`yt-dlp-o ${filepath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (/* error: any, stdout: any, stderr: any */) => {
+ // const time = Array.from(stdout.trim().split(':')).reverse();
+ // const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+ const data = { size: 0, filepath, name, mimetype: 'video', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' };
+ const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), toJson: () => undefined as any }) };
+ MoveParsedFile(file, Directory.videos).then(output => res(output));
+ });
+ }
+ });
+ }
});
- //data && bufferConverterRec(data);
- return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
- };
+ }
+ const manualSuffixes = ['.webm'];
- const { pngs, jpgs, webps, tiffs } = AcceptableMedia;
- const pngOptions = {
- compressionLevel: 9,
- adaptiveFiltering: true,
- force: true,
- };
+ async function UploadAudio(file: File, format: string) {
+ const suffix = manualSuffixes.includes(format) ? format : undefined;
+ return MoveParsedFile(file, Directory.audio, suffix);
+ }
- async function correctRotation(imgSourcePath: string) {
- const buffer = fs.readFileSync(imgSourcePath);
- try {
- return (await autorotate.rotate(buffer, { quality: 30 })).buffer;
- } catch (e) {
- return buffer;
+ async function UploadPdf(file: File) {
+ const fileKey = (await md5File(file.filepath)) + '.pdf';
+ const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+ if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
+ fs.unlink(file.filepath, () => {});
+ return new Promise<Upload.FileResponse>(res => {
+ const pdfTextFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+ const readStream = createReadStream(serverPathToFile(Directory.text, pdfTextFilename));
+ let rawText = '';
+ readStream
+ .on('data', chunk => {
+ rawText += chunk.toString();
+ })
+ .on('end', () => res(resolveExistingFile(file.originalFilename ?? '', fileKey, Directory.pdfs, file.mimetype, undefined, rawText)));
+ });
}
+ const dataBuffer = readFileSync(file.filepath);
+ const result: ParsedPDF | any = await parse(dataBuffer).catch((e: any) => e);
+ if (!result.code) {
+ await new Promise<void>((resolve, reject) => {
+ const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
+ writeStream.write(result?.text, error => (error ? reject(error) : resolve()));
+ });
+ return MoveParsedFile(file, Directory.pdfs, undefined, result?.text, undefined, fileKey);
+ }
+ return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.originalFilename}).${result.message}` } };
}
- /**
- * 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 imgSourcePath file path for image being resized
- * @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(imgSourcePath: string, outputFileName: string, outputDirectory: string) {
- const writtenFiles: { [suffix: string]: string } = {};
- const sizes = imageResampleSizes(path.extname(outputFileName));
+ async function UploadCsv(file: File) {
+ const { filepath: sourcePath } = file;
+ // read the file as a string
+ const data = readFileSync(sourcePath, 'utf8');
+ // split the string into an array of lines
+ return MoveParsedFile(file, Directory.csv, undefined, data);
+ // console.log(csvParser(data));
+ }
- const imgBuffer = await correctRotation(imgSourcePath);
- const imgReadStream = new Duplex();
- imgReadStream.push(imgBuffer);
- imgReadStream.push(null);
- const outputPath = (suffix: SizeSuffix) => path.resolve(outputDirectory, (writtenFiles[suffix] = InjectSize(outputFileName, suffix)));
- await Promise.all(
- sizes.filter(({ width }) => !width).map(({ suffix }) =>
- new Promise<void>(res => imgReadStream.pipe(createWriteStream(outputPath(suffix))).on('close', res))
- )); // prettier-ignore
+ export async function upload(file: File /* , overwriteGuid?: string */): Promise<Upload.FileResponse> {
+ // const isAzureOn = usingAzure();
+ const { mimetype, filepath, originalFilename } = file;
+ const types = mimetype?.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.
- return Jimp.read(imgBuffer)
- .then(async (img: any) => {
- await Promise.all( sizes.filter(({ width }) => width).map(({ width, suffix }) =>
- img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
- )); // prettier-ignore
- return writtenFiles;
- })
- .catch((e: any) => {
- console.log('ERROR' + e);
- return writtenFiles;
- });
+ const category = types[0];
+ let format = `.${types[1]}`;
+ console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${mimetype}) in category (${category}).`));
+
+ switch (category) {
+ case 'image':
+ if (imageFormats.includes(format)) {
+ const result = await UploadImage(filepath, basename(filepath));
+ return { source: file, result };
+ }
+ fs.unlink(filepath, () => {});
+ return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .jpg` } };
+ case 'video': {
+ const vidFile = file;
+ if (format.includes('x-matroska')) {
+ await new Promise(res => {
+ ffmpeg(vidFile.filepath)
+ .videoCodec('copy') // this will copy the data instead of reencode it
+ .save(vidFile.filepath.replace('.mkv', '.mp4'))
+ .on('end', res)
+ .on('error', (e: any) => console.log(e));
+ });
+ vidFile.filepath = vidFile.filepath.replace('.mkv', '.mp4');
+ format = '.mp4';
+ }
+ if (format.includes('quicktime')) {
+ let abort = false;
+ await new Promise<void>(res => {
+ ffmpeg.ffprobe(vidFile.filepath, (err: any, metadata: any) => {
+ if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) {
+ abort = true;
+ }
+ res();
+ });
+ });
+ if (abort) {
+ // bcz: instead of aborting, we could convert the file using the code below to an mp4. Problem is that this takes a long time and will clog up the server.
+ // await new Promise(res =>
+ // ffmpeg(file.path)
+ // .videoCodec('libx264') // this will copy the data instead of reencode it
+ // .audioCodec('mp2')
+ // .save(vidFile.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
+ // .on('end', res)
+ // );
+ // vidFile.path = vidFile.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
+ // format = '.mp4';
+ fs.unlink(filepath, () => {});
+ return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
+ }
+ }
+ if (videoFormats.includes(format) || format.includes('.webm')) {
+ return MoveParsedFile(vidFile, Directory.videos);
+ }
+ fs.unlink(filepath, () => {});
+ return { source: vidFile, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
+ }
+ case 'application':
+ if (applicationFormats.includes(format)) {
+ const val = UploadPdf(file);
+ if (val) return val;
+ }
+ break;
+ case 'audio': {
+ const components = format.split(';');
+ if (components.length > 1) {
+ [format] = components;
+ }
+ if (audioFormats.includes(format)) {
+ return UploadAudio(file, format);
+ }
+ fs.unlink(filepath, () => {});
+ return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp3` } };
+ }
+ case 'text':
+ if (types[1] === 'csv') {
+ return UploadCsv(file);
+ }
+ break;
+ default:
+ }
+
+ console.log(red(`Ignoring unsupported file (${originalFilename}) with upload type (${mimetype}).`));
+ fs.unlink(filepath, () => {});
+ return { source: file, result: new Error(`Could not upload unsupported file (${originalFilename}) with upload type (${mimetype}).`) };
}
- /**
- * define the resizers to use
- * @param ext the extension
- * @returns an array of resize descriptions
- */
- export function imageResampleSizes(ext: string): DashUploadUtils.ImageResizer[] {
- return [
- { suffix: SizeSuffix.Original, width: 0 },
- ...[...(AcceptableMedia.imageFormats.includes(ext.toLowerCase()) ? Object.values(DashUploadUtils.Sizes) : [])].map(({ suffix, width }) => ({
- width,
- suffix,
- })),
- ];
+ export async function buildFileDirectories() {
+ if (!existsSync(publicDirectory)) {
+ console.error('\nPlease ensure that the following directory exists...\n');
+ console.log(publicDirectory);
+ process.exit(0);
+ }
+ if (!existsSync(filesDirectory)) {
+ console.error('\nPlease ensure that the following directory exists...\n');
+ console.log(filesDirectory);
+ process.exit(0);
+ }
+ const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`));
+ return Promise.all(pending);
+ }
+
+ export interface RequestedImageSize {
+ width: number;
+ height: number;
+ type: string;
+ }
+
+ export interface ImageResizer {
+ width: number;
+ suffix: SizeSuffix;
}
}
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 423c719c2..041f65592 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -1,11 +1,15 @@
+/* eslint-disable no-await-in-loop */
+/* eslint-disable no-continue */
+/* eslint-disable no-cond-assign */
+/* eslint-disable no-restricted-syntax */
import * as fs from 'fs';
import * as path from 'path';
import { Database } from './database';
import { Search } from './Search';
-
function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
for (const key in doc) {
+ // eslint-disable-next-line no-prototype-builtins
if (!doc.hasOwnProperty(key)) {
continue;
}
@@ -13,22 +17,22 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
if (field === undefined || field === null) {
continue;
}
- if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+ if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
ids.push(field.fieldId);
- } else if (field.__type === "list") {
+ } else if (field.__type === 'list') {
addDoc(field.fields, ids, files);
- } else if (typeof field === "string") {
- const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
+ } else if (typeof field === 'string') {
+ const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w-]*)"/g;
let match: string[] | null;
while ((match = re.exec(field)) !== null) {
ids.push(match[1]);
}
- } else if (field.__type === "RichTextField") {
+ } else if (field.__type === 'RichTextField') {
const re = /"href"\s*:\s*"(.*?)"/g;
let match: string[] | null;
while ((match = re.exec(field.Data)) !== null) {
const urlString = match[1];
- const split = new URL(urlString).pathname.split("doc/");
+ const split = new URL(urlString).pathname.split('doc/');
if (split.length > 1) {
ids.push(split[split.length - 1]);
}
@@ -36,7 +40,7 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
const re2 = /"src"\s*:\s*"(.*?)"/g;
while ((match = re2.exec(field.Data)) !== null) {
const urlString = match[1];
- const pathname = new URL(urlString).pathname;
+ const { pathname } = new URL(urlString);
const ext = path.extname(pathname);
const fileName = path.basename(pathname, ext);
let exts = files[fileName];
@@ -45,9 +49,9 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
}
exts.push(ext);
}
- } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
+ } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
const url = new URL(field.url);
- const pathname = url.pathname;
+ const { pathname } = url;
const ext = path.extname(pathname);
const fileName = path.basename(pathname, ext);
let exts = files[fileName];
@@ -60,12 +64,12 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
}
async function GarbageCollect(full: boolean = true) {
- console.log("start GC");
+ console.log('start GC');
const start = Date.now();
// await new Promise(res => setTimeout(res, 3000));
const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users');
const users = await cursor.toArray();
- const ids: string[] = [...users.map((user:any) => user.userDocumentId), ...users.map((user:any) => user.sharingDocumentId), ...users.map((user:any) => user.linkDatabaseId)];
+ const ids: string[] = [...users.map((user: any) => user.userDocumentId), ...users.map((user: any) => user.sharingDocumentId), ...users.map((user: any) => user.linkDatabaseId)];
const visited = new Set<string>();
const files: { [name: string]: string[] } = {};
@@ -76,9 +80,11 @@ async function GarbageCollect(full: boolean = true) {
if (!fetchIds.length) {
continue;
}
- const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res));
+ const docs = await new Promise<{ [key: string]: any }[]>(res => {
+ Database.Instance.getDocuments(fetchIds, res);
+ });
for (const doc of docs) {
- const id = doc.id;
+ const { id } = doc;
if (doc === undefined) {
console.log(`Couldn't find field with Id ${id}`);
continue;
@@ -95,19 +101,27 @@ async function GarbageCollect(full: boolean = true) {
const notToDelete = Array.from(visited);
const toDeleteCursor = await Database.Instance.query({ _id: { $nin: notToDelete } }, { _id: 1 });
- const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc:any) => doc._id);
+ const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc: any) => doc._id);
toDeleteCursor.close();
if (!full) {
- await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { "deleted": true } });
- await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { "deleted": true } });
- console.log(await Search.updateDocuments(
- notToDelete.map<any>(id => ({
- id, deleted: { set: null }
- }))
- .concat(toDelete.map(id => ({
- id, deleted: { set: true }
- })))));
- console.log("Done with partial GC");
+ await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { deleted: true } });
+ await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { deleted: true } });
+ console.log(
+ await Search.updateDocuments(
+ notToDelete
+ .map<any>(id => ({
+ id,
+ deleted: { set: null },
+ }))
+ .concat(
+ toDelete.map(id => ({
+ id,
+ deleted: { set: true },
+ }))
+ )
+ )
+ );
+ console.log('Done with partial GC');
console.log(`Took ${(Date.now() - start) / 1000} seconds`);
} else {
let i = 0;
@@ -123,15 +137,15 @@ async function GarbageCollect(full: boolean = true) {
console.log(`${deleted} documents deleted`);
await Search.deleteDocuments(toDelete);
- console.log("Cleared search documents");
+ console.log('Cleared search documents');
- const folder = "./src/server/public/files/";
+ const folder = './src/server/public/files/';
fs.readdir(folder, (_, fileList) => {
const filesToDelete = fileList.filter(file => {
const ext = path.extname(file);
let base = path.basename(file, ext);
const existsInDb = (base in files || (base = base.substring(0, base.length - 2)) in files) && files[base].includes(ext);
- return file !== ".gitignore" && !existsInDb;
+ return file !== '.gitignore' && !existsInDb;
});
console.log(`Deleting ${filesToDelete.length} files`);
filesToDelete.forEach(file => {
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index b74332bf5..1432d91c4 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -3,16 +3,15 @@ import { DocumentsCollection, IDatabase } from './IDatabase';
import { Transferable } from './Message';
export class MemoryDatabase implements IDatabase {
-
private db: { [collectionName: string]: { [id: string]: any } } = {};
private getCollection(collectionName: string) {
const collection = this.db[collectionName];
if (collection) {
return collection;
- } else {
- return this.db[collectionName] = {};
}
+ this.db[collectionName] = {};
+ return {};
}
public getCollectionNames() {
@@ -21,15 +20,15 @@ export class MemoryDatabase implements IDatabase {
public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
const collection = this.getCollection(collectionName);
- const set = "$set";
+ const set = '$set';
if (set in value) {
let currentVal = collection[id] ?? (collection[id] = {});
const val = value[set];
for (const key in val) {
- const keys = key.split(".");
+ const keys = key.split('.');
for (let i = 0; i < keys.length - 1; i++) {
const k = keys[i];
- if (typeof currentVal[k] === "object") {
+ if (typeof currentVal[k] === 'object') {
currentVal = currentVal[k];
} else {
currentVal[k] = {};
@@ -45,7 +44,7 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve(undefined);
}
- public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.UpdateResult> {
+ public updateMany(/* query: any, update: any, collectionName = DocumentsCollection */): Promise<mongodb.UpdateResult> {
throw new Error("Can't updateMany a MemoryDatabase");
}
@@ -54,7 +53,9 @@ export class MemoryDatabase implements IDatabase {
}
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>;
+ // eslint-disable-next-line no-dupe-class-members
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>;
+ // eslint-disable-next-line no-dupe-class-members
public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteResult> {
const i = id.id ?? id;
delete this.getCollection(collectionName)[i];
@@ -75,7 +76,7 @@ export class MemoryDatabase implements IDatabase {
}
public insert(value: any, collectionName = DocumentsCollection): Promise<void> {
- const id = value.id;
+ const { id } = value;
this.getCollection(collectionName)[id] = value;
return Promise.resolve();
}
@@ -93,14 +94,18 @@ export class MemoryDatabase implements IDatabase {
const count = Math.min(ids.length, 1000);
const index = ids.length - count;
const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
- if (!fetchIds.length) {
- continue;
- }
- const docs = await new Promise<{ [key: string]: any }[]>(res => this.getDocuments(fetchIds, res, collectionName));
- for (const doc of docs) {
- const id = doc.id;
- visited.add(id);
- ids.push(...(await fn(doc)));
+ if (fetchIds.length) {
+ // eslint-disable-next-line no-await-in-loop
+ const docs = await new Promise<{ [key: string]: any }[]>(res => {
+ this.getDocuments(fetchIds, res, collectionName);
+ });
+ // eslint-disable-next-line no-restricted-syntax
+ for (const doc of docs) {
+ const { id } = doc;
+ visited.add(id);
+ // eslint-disable-next-line no-await-in-loop
+ ids.push(...(await fn(doc)));
+ }
}
}
}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 8f0af08bc..03150c841 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,22 +1,47 @@
-import { Point } from "../pen-gestures/ndollar";
-import { Utils } from "../Utils";
+import * as uuid from 'uuid';
+import { Point } from '../pen-gestures/ndollar';
+function GenerateDeterministicGuid(seed: string): string {
+ return uuid.v5(seed, uuid.v5.URL);
+}
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class Message<T> {
private _name: string;
private _guid: string;
constructor(name: string) {
this._name = name;
- this._guid = Utils.GenerateDeterministicGuid(name);
+ this._guid = GenerateDeterministicGuid(name);
}
- get Name(): string { return this._name; }
- get Message(): string { return this._guid; }
+ get Name(): string {
+ return this._name;
+ }
+ get Message(): string {
+ return this._guid;
+ }
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
- Html, Video, Audio, Ink, PDF, Tuple, Boolean, Script, Templates
+ Number,
+ List,
+ Key,
+ Image,
+ Web,
+ Document,
+ Text,
+ Icon,
+ RichText,
+ DocumentReference,
+ Html,
+ Video,
+ Audio,
+ Ink,
+ PDF,
+ Tuple,
+ Boolean,
+ Script,
+ Templates,
}
export interface Transferable {
@@ -26,7 +51,9 @@ export interface Transferable {
}
export enum YoutubeQueryTypes {
- Channels, SearchVideo, VideoDetails
+ Channels,
+ SearchVideo,
+ VideoDetails,
}
export interface YoutubeQueryInput {
@@ -45,7 +72,7 @@ export interface Diff extends Reference {
export interface GestureContent {
readonly points: Array<Point>;
- readonly bounds: { right: number, left: number, bottom: number, top: number, width: number, height: number };
+ readonly bounds: { right: number; left: number; bottom: number; top: number; width: number; height: number };
readonly width?: string;
readonly color?: string;
}
@@ -73,27 +100,27 @@ export interface RoomMessage {
}
export namespace MessageStore {
- export const Foo = new Message<string>("Foo");
- export const Bar = new Message<string>("Bar");
- export const SetField = new Message<Transferable>("Set Field"); // send Transferable (no reply)
- export const GetField = new Message<string>("Get Field"); // send string 'id' get Transferable back
- export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
- export const GetDocument = new Message<string>("Get Document");
- export const DeleteAll = new Message<any>("Delete All");
- export const ConnectionTerminated = new Message<string>("Connection Terminated");
-
- export const GesturePoints = new Message<GestureContent>("Gesture Points");
- export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
- export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>("Update Mobile Ink Overlay Position");
- export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>("Upload Document From Mobile");
-
- export const GetRefField = new Message<string>("Get Ref Field");
- export const GetRefFields = new Message<string[]>("Get Ref Fields");
- export const UpdateField = new Message<Diff>("Update Ref Field");
- export const CreateField = new Message<Reference>("Create Ref Field");
- export const YoutubeApiQuery = new Message<YoutubeQueryInput>("Youtube Api Query");
- export const DeleteField = new Message<string>("Delete field");
- export const DeleteFields = new Message<string[]>("Delete fields");
-
- export const UpdateStats = new Message<string>("updatestats");
+ export const Foo = new Message<string>('Foo');
+ export const Bar = new Message<string>('Bar');
+ export const SetField = new Message<Transferable>('Set Field'); // send Transferable (no reply)
+ export const GetField = new Message<string>('Get Field'); // send string 'id' get Transferable back
+ export const GetFields = new Message<string[]>('Get Fields'); // send string[] of 'id' get Transferable[] back
+ export const GetDocument = new Message<string>('Get Document');
+ export const DeleteAll = new Message<any>('Delete All');
+ export const ConnectionTerminated = new Message<string>('Connection Terminated');
+
+ export const GesturePoints = new Message<GestureContent>('Gesture Points');
+ export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>('Trigger Mobile Ink Overlay');
+ export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>('Update Mobile Ink Overlay Position');
+ export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>('Upload Document From Mobile');
+
+ export const GetRefField = new Message<string>('Get Ref Field');
+ export const GetRefFields = new Message<string[]>('Get Ref Fields');
+ export const UpdateField = new Message<Diff>('Update Ref Field');
+ export const CreateField = new Message<Reference>('Create Ref Field');
+ export const YoutubeApiQuery = new Message<YoutubeQueryInput>('Youtube Api Query');
+ export const DeleteField = new Message<string>('Delete field');
+ export const DeleteFields = new Message<string[]>('Delete fields');
+
+ export const UpdateStats = new Message<string>('updatestats');
}
diff --git a/src/server/PdfTypes.ts b/src/server/PdfTypes.ts
index e87f08e1d..fb435a677 100644
--- a/src/server/PdfTypes.ts
+++ b/src/server/PdfTypes.ts
@@ -1,21 +1,19 @@
-export interface ParsedPDF {
- numpages: number;
- numrender: number;
- info: PDFInfo;
- metadata: PDFMetadata;
- version: string; //https://mozilla.github.io/pdf.js/getting_started/
- text: string;
-}
-
export interface PDFInfo {
PDFFormatVersion: string;
IsAcroFormPresent: boolean;
IsXFAPresent: boolean;
[key: string]: any;
}
-
export interface PDFMetadata {
parse(): void;
get(name: string): string;
has(name: string): boolean;
-} \ No newline at end of file
+}
+export interface ParsedPDF {
+ numpages: number;
+ numrender: number;
+ info: PDFInfo;
+ metadata: PDFMetadata;
+ version: string; // https://mozilla.github.io/pdf.js/getting_started/
+ text: string;
+}
diff --git a/src/server/ProcessFactory.ts b/src/server/ProcessFactory.ts
index f69eda4c3..3791b0e1e 100644
--- a/src/server/ProcessFactory.ts
+++ b/src/server/ProcessFactory.ts
@@ -1,44 +1,42 @@
-import { ChildProcess, spawn, StdioOptions } from "child_process";
-import { existsSync, mkdirSync } from "fs";
-import { Stream } from "stream";
+import { ChildProcess, spawn, StdioOptions } from 'child_process';
+import { existsSync, mkdirSync } from 'fs';
+import { rimraf } from 'rimraf';
+import { Stream } from 'stream';
import { fileDescriptorFromStream, pathFromRoot } from './ActionUtilities';
-import { rimraf } from "rimraf";
-
-export namespace ProcessFactory {
-
- export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined;
-
- export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise<ChildProcess> {
- if (stdio === "logfile") {
- const log_fd = await Logger.create(command, args);
- stdio = ["ignore", log_fd, log_fd];
- }
- const child = spawn(command, args ?? [], { detached, stdio });
- child.unref();
- return child;
- }
-
-}
export namespace Logger {
-
- const logPath = pathFromRoot("./logs");
+ const logPath = pathFromRoot('./logs');
export async function initialize() {
if (existsSync(logPath)) {
if (!process.env.SPAWNED) {
- await new Promise<any>(resolve => rimraf(logPath).then(resolve));
+ await new Promise<any>(resolve => {
+ rimraf(logPath).then(resolve);
+ });
}
}
mkdirSync(logPath);
}
- export async function create(command: string, args?: readonly string[]): Promise<number> {
- return fileDescriptorFromStream(generate_log_path(command, args));
+ function generateLogPath(command: string, args?: readonly string[]) {
+ return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
}
- function generate_log_path(command: string, args?: readonly string[]) {
- return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
+ export async function create(command: string, args?: readonly string[]): Promise<number> {
+ return fileDescriptorFromStream(generateLogPath(command, args));
}
+}
+export namespace ProcessFactory {
+ export type Sink = 'pipe' | 'ipc' | 'ignore' | 'inherit' | Stream | number | null | undefined;
-} \ No newline at end of file
+ export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | 'logfile', detached = true): Promise<ChildProcess> {
+ if (stdio === 'logfile') {
+ const logFd = await Logger.create(command, args);
+ // eslint-disable-next-line no-param-reassign
+ stdio = ['ignore', logFd, logFd];
+ }
+ const child = spawn(command, args ?? [], { detached, stdio });
+ child.unref();
+ return child;
+ }
+}
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 540bca776..d8e0455f6 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,9 +1,9 @@
import { cyan, green, red } from 'colors';
import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from '.';
import { Utils } from '../Utils';
-import { DashUserModel } from './authentication/DashUserModel';
import RouteSubscriber from './RouteSubscriber';
+import { AdminPrivileges } from './SocketData';
+import { DashUserModel } from './authentication/DashUserModel';
export enum Method {
GET,
@@ -21,6 +21,34 @@ export type SecureHandler = (core: AuthorizedCore) => any | Promise<any>;
export type PublicHandler = (core: CoreArguments) => any | Promise<any>;
export type ErrorHandler = (core: CoreArguments & { error: any }) => any | Promise<any>;
+export const STATUS = {
+ OK: 200,
+ BAD_REQUEST: 400,
+ EXECUTION_ERROR: 500,
+ PERMISSION_DENIED: 403,
+};
+
+export function _error(res: Response, message: string, error?: any) {
+ console.error(message, error);
+ res.statusMessage = message;
+ res.status(STATUS.EXECUTION_ERROR).send(error);
+}
+
+export function _success(res: Response, body: any) {
+ res.status(STATUS.OK).send(body);
+}
+
+export function _invalid(res: Response, message: string) {
+ res.statusMessage = message;
+ res.status(STATUS.BAD_REQUEST).send();
+}
+
+export function _permissionDenied(res: Response, message?: string) {
+ if (message) {
+ res.statusMessage = message;
+ }
+ res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`);
+}
export interface RouteInitializer {
method: Method;
subscription: string | RouteSubscriber | (string | RouteSubscriber)[];
@@ -71,7 +99,7 @@ export default class RouteManager {
console.log('please remove all duplicate routes before continuing');
}
if (malformedCount) {
- console.log(`please ensure all routes adhere to ^\/$|^\/[A-Za-z]+(\/\:[A-Za-z?_]+)*$`);
+ console.log(`please ensure all routes adhere to ^/$|^/[A-Za-z]+(/:[A-Za-z?_]+)*$`);
}
process.exit(1);
} else {
@@ -94,7 +122,7 @@ export default class RouteManager {
typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
initializer.subscription instanceof Array &&
- initializer.subscription.map(sub => {
+ initializer.subscription.forEach(sub => {
typeof sub === 'string' && RouteManager.routes.push(sub);
sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
});
@@ -120,23 +148,23 @@ export default class RouteManager {
};
if (user) {
if (requireAdmin && isRelease && process.env.PASSWORD) {
- if (AdminPriviliges.get(user.id)) {
- AdminPriviliges.delete(user.id);
+ if (AdminPrivileges.get(user.id)) {
+ AdminPrivileges.delete(user.id);
} else {
- return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
+ res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
+ return;
}
}
await tryExecute(secureHandler, { ...core, user });
- } else {
- //req.session!.target = target;
- if (publicHandler) {
- await tryExecute(publicHandler, core);
- if (!res.headersSent) {
- // res.redirect("/login");
- }
- } else {
- res.redirect('/login');
+ }
+ // req.session!.target = target;
+ else if (publicHandler) {
+ await tryExecute(publicHandler, core);
+ if (!res.headersSent) {
+ // res.redirect("/login");
}
+ } else {
+ res.redirect('/login');
}
setTimeout(() => {
if (!res.headersSent) {
@@ -153,7 +181,7 @@ export default class RouteManager {
} else {
route = subscriber.build;
}
- if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
+ if (!/^\/$|^\/[A-Za-z*]+(\/:[A-Za-z?_*]+)*$/g.test(route)) {
this.failedRegistrations.push({
reason: RegistrationError.Malformed,
route,
@@ -180,6 +208,7 @@ export default class RouteManager {
case Method.POST:
this.server.post(route, supervised);
break;
+ default:
}
}
};
@@ -190,32 +219,3 @@ export default class RouteManager {
}
};
}
-
-export const STATUS = {
- OK: 200,
- BAD_REQUEST: 400,
- EXECUTION_ERROR: 500,
- PERMISSION_DENIED: 403,
-};
-
-export function _error(res: Response, message: string, error?: any) {
- console.error(message, error);
- res.statusMessage = message;
- res.status(STATUS.EXECUTION_ERROR).send(error);
-}
-
-export function _success(res: Response, body: any) {
- res.status(STATUS.OK).send(body);
-}
-
-export function _invalid(res: Response, message: string) {
- res.statusMessage = message;
- res.status(STATUS.BAD_REQUEST).send();
-}
-
-export function _permission_denied(res: Response, message?: string) {
- if (message) {
- res.statusMessage = message;
- }
- res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`);
-}
diff --git a/src/server/RouteSubscriber.ts b/src/server/RouteSubscriber.ts
index a1cf7c1c4..b923805a8 100644
--- a/src/server/RouteSubscriber.ts
+++ b/src/server/RouteSubscriber.ts
@@ -18,9 +18,8 @@ export default class RouteSubscriber {
public get build() {
let output = this._root;
if (this.requestParameters.length) {
- output = `${output}/:${this.requestParameters.join("/:")}`;
+ output = `${output}/:${this.requestParameters.join('/:')}`;
}
return output;
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 7db1c2dae..0a961f570 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -22,24 +22,17 @@ export namespace Upload {
return 'duration' in uploadResponse;
}
+ export interface AccessPathInfo {
+ [suffix: string]: { client: string; server: string };
+ }
export interface FileInformation {
accessPaths: AccessPathInfo;
rawText?: string;
duration?: number;
}
-
- export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
-
- export type ImageInformation = FileInformation & InspectionResults;
-
- export type VideoInformation = FileInformation & VideoResults;
-
- export interface AccessPathInfo {
- [suffix: string]: { client: string; server: string };
- }
-
- export interface VideoResults {
- duration: number;
+ export interface EnrichedExifData {
+ data: ExifData & ExifData['gps'];
+ error?: string;
}
export interface InspectionResults {
source: string;
@@ -51,9 +44,13 @@ export namespace Upload {
nativeHeight: number;
filename?: string;
}
-
- export interface EnrichedExifData {
- data: ExifData & ExifData['gps'];
- error?: string;
+ export interface VideoResults {
+ duration: number;
}
+
+ export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
+
+ export type ImageInformation = FileInformation & InspectionResults;
+
+ export type VideoInformation = FileInformation & VideoResults;
}
diff --git a/src/server/SocketData.ts b/src/server/SocketData.ts
new file mode 100644
index 000000000..e857996e5
--- /dev/null
+++ b/src/server/SocketData.ts
@@ -0,0 +1,35 @@
+import { Socket } from 'socket.io';
+import * as path from 'path';
+
+export const timeMap: { [id: string]: number } = {};
+export const userOperations = new Map<string, number>();
+export const socketMap = new Map<Socket, string>();
+
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
+
+export const AdminPrivileges: Map<string, boolean> = new Map();
+
+export const resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 };
+
+export enum Directory {
+ parsed_files = 'parsed_files',
+ images = 'images',
+ videos = 'videos',
+ pdfs = 'pdfs',
+ text = 'text',
+ audio = 'audio',
+ csv = 'csv',
+}
+
+export function serverPathToFile(directory: Directory, filename: string) {
+ return path.normalize(`${filesDirectory}/${directory}/${filename}`);
+}
+
+export function pathToDirectory(directory: Directory) {
+ return path.normalize(`${filesDirectory}/${directory}`);
+}
+
+export function clientPathToFile(directory: Directory, filename: string) {
+ return `/files/${directory}/${filename}`;
+}
diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts
index ef1f9a91e..46dc00b8a 100644
--- a/src/server/apis/google/CredentialsLoader.ts
+++ b/src/server/apis/google/CredentialsLoader.ts
@@ -1,10 +1,9 @@
-import { readFile, readFileSync } from "fs";
-import { pathFromRoot } from "../../ActionUtilities";
-import { SecureContextOptions } from "tls";
-import { blue, red } from "colors";
+import { readFile, readFileSync } from 'fs';
+import { SecureContextOptions } from 'tls';
+import { blue, red } from 'colors';
+import { pathFromRoot } from '../../ActionUtilities';
export namespace GoogleCredentialsLoader {
-
export interface InstalledCredentials {
client_id: string;
project_id: string;
@@ -28,18 +27,16 @@ export namespace GoogleCredentialsLoader {
});
});
}
-
}
export namespace SSL {
-
export let Credentials: SecureContextOptions = {};
export let Loaded = false;
const suffixes = {
- privateKey: ".key",
- certificate: ".crt",
- caBundle: "-ca.crt"
+ privateKey: '.key',
+ certificate: '.crt',
+ caBundle: '-ca.crt',
};
export async function loadCredentials() {
@@ -57,11 +54,10 @@ export namespace SSL {
}
export function exit() {
- console.log(red("Running this server in release mode requires the following SSL credentials in the project root:"));
- const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}";
+ console.log(red('Running this server in release mode requires the following SSL credentials in the project root:'));
+ const serverName = process.env.serverName ? process.env.serverName : '{process.env.serverName}';
Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`)));
- console.log(red("Please ensure these files exist and restart, or run this in development mode."));
+ console.log(red('Please ensure these files exist and restart, or run this in development mode.'));
process.exit(0);
}
-
}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 3940b7a3d..d3acc968b 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -1,9 +1,9 @@
-import { google } from 'googleapis';
-import { OAuth2Client, Credentials, OAuth2ClientOptions } from 'google-auth-library';
-import { Opt } from '../../../fields/Doc';
import { GaxiosResponse } from 'gaxios';
-import * as request from 'request-promise';
+import { Credentials, OAuth2Client, OAuth2ClientOptions } from 'google-auth-library';
+import { google } from 'googleapis';
import * as qs from 'query-string';
+import * as request from 'request-promise';
+import { Opt } from '../../../fields/Doc';
import { Database } from '../../database';
import { GoogleCredentialsLoader } from './CredentialsLoader';
@@ -57,12 +57,12 @@ export namespace GoogleApiServerUtils {
* global, intentionally unauthenticated worker OAuth2 client instance.
*/
export function processProjectCredentials(): void {
- const { client_secret, client_id, redirect_uris } = GoogleCredentialsLoader.ProjectCredentials;
+ const { client_secret: clientSecret, client_id: clientId, redirect_uris: redirectUris } = GoogleCredentialsLoader.ProjectCredentials;
// initialize the global authorization client
oAuthOptions = {
- clientId: client_id,
- clientSecret: client_secret,
- redirectUri: redirect_uris[0],
+ clientId,
+ clientSecret,
+ redirectUri: redirectUris[0],
};
worker = generateClient();
}
diff --git a/src/server/apis/google/SharedTypes.ts b/src/server/apis/google/SharedTypes.ts
index 9ad6130b6..f5e0e2e2b 100644
--- a/src/server/apis/google/SharedTypes.ts
+++ b/src/server/apis/google/SharedTypes.ts
@@ -1,9 +1,3 @@
-export interface NewMediaItemResult {
- uploadToken: string;
- status: { code: number, message: string };
- mediaItem: MediaItem;
-}
-
export interface MediaItem {
id: string;
description: string;
@@ -17,5 +11,10 @@ export interface MediaItem {
};
filename: string;
}
+export interface NewMediaItemResult {
+ uploadToken: string;
+ status: { code: number; message: string };
+ mediaItem: MediaItem;
+}
-export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] }; \ No newline at end of file
+export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] };
diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts
index 427f54608..97c3f0b54 100644
--- a/src/server/apis/youtube/youtubeApiSample.d.ts
+++ b/src/server/apis/youtube/youtubeApiSample.d.ts
@@ -1,2 +1,2 @@
declare const YoutubeApi: any;
-export = YoutubeApi; \ No newline at end of file
+export = YoutubeApi;
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 9c1525df0..0cc1553c0 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,21 +1,21 @@
-import { default as User, DashUserModel, initializeGuest } from './DashUserModel';
-import { Request, Response, NextFunction } from 'express';
-import * as passport from 'passport';
-import { IVerifyOptions } from 'passport-local';
-import './Passport';
import * as async from 'async';
-import * as nodemailer from 'nodemailer';
import * as c from 'crypto';
-import { emptyFunction, Utils } from '../../Utils';
-import { MailOptions } from 'nodemailer/lib/stream-transport';
+import { NextFunction, Request, Response } from 'express';
import { check, validationResult } from 'express-validator';
+import * as nodemailer from 'nodemailer';
+import { MailOptions } from 'nodemailer/lib/stream-transport';
+import * as passport from 'passport';
+import { Utils } from '../../Utils';
+import User, { DashUserModel, initializeGuest } from './DashUserModel';
+import './Passport';
+// import { IVerifyOptions } from 'passport-local';
/**
* GET /signup
* Directs user to the signup page
* modeled by signup.pug in views
*/
-export let getSignup = (req: Request, res: Response) => {
+export const getSignup = (req: Request, res: Response) => {
if (req.user) {
return res.redirect('/home');
}
@@ -23,13 +23,23 @@ export let getSignup = (req: Request, res: Response) => {
title: 'Sign Up',
user: req.user,
});
+ return undefined;
+};
+
+const tryRedirectToTarget = (req: Request, res: Response) => {
+ const target = (req.session as any)?.target;
+ if (req.session && target) {
+ res.redirect(target);
+ } else {
+ res.redirect('/home');
+ }
};
/**
* POST /signup
* Create a new local account.
*/
-export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+export const postSignup = (req: Request, res: Response, next: NextFunction) => {
const email = req.body.email as String;
check('email', 'Email is not valid').isEmail().run(req);
check('password', 'Password must be at least 4 characters long').isLength({ min: 4 }).run(req);
@@ -42,7 +52,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
return res.redirect('/signup');
}
- const password = req.body.password;
+ const { password } = req.body;
const model = {
email,
@@ -65,35 +75,29 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
req.logIn(user, err => {
if (err) return next(err);
tryRedirectToTarget(req, res);
+ return undefined;
});
})
.catch((err: any) => next(err));
+ return undefined;
})
.catch((err: any) => next(err));
+ return undefined;
};
-
-const tryRedirectToTarget = (req: Request, res: Response) => {
- const target = (req.session as any)?.target;
- if (req.session && target) {
- res.redirect(target);
- } else {
- res.redirect('/home');
- }
-};
-
/**
* GET /login
* Login page.
*/
-export let getLogin = (req: Request, res: Response) => {
+export const getLogin = (req: Request, res: Response) => {
if (req.user) {
- //req.session.target = undefined;
+ // req.session.target = undefined;
return res.redirect('/home');
}
res.render('login.pug', {
title: 'Log In',
user: req.user,
});
+ return undefined;
};
/**
@@ -101,7 +105,7 @@ export let getLogin = (req: Request, res: Response) => {
* Sign in using email and password.
* On failure, redirect to signup page
*/
-export let postLogin = (req: Request, res: Response, next: NextFunction) => {
+export const postLogin = (req: Request, res: Response, next: NextFunction) => {
if (req.body.email === '') {
User.findOne({ email: 'guest' })
.then((user: any) => !user && initializeGuest())
@@ -119,23 +123,21 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
return res.redirect('/signup');
}
- const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
+ const callback = (err: Error, user: DashUserModel /* , _info: IVerifyOptions */) => {
if (err) {
next(err);
- return;
- }
- if (!user) {
+ } else if (!user) {
return res.redirect('/signup');
- }
- req.logIn(user, err => {
- if (err) {
- next(err);
- return;
- }
- tryRedirectToTarget(req, res);
- });
+ } else
+ req.logIn(user, loginErr => {
+ if (loginErr) {
+ next(loginErr);
+ } else tryRedirectToTarget(req, res);
+ });
+ return undefined;
};
setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500);
+ return undefined;
};
/**
@@ -143,31 +145,29 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
* Invokes the logout function on the request
* and destroys the user's current session.
*/
-export let getLogout = (req: Request, res: Response) => {
+export const getLogout = (req: Request, res: Response) => {
req.logout(err => {
if (err) console.log(err);
else res.redirect('/login');
});
};
-export let getForgot = function (req: Request, res: Response) {
+export const getForgot = function (req: Request, res: Response) {
res.render('forgot.pug', {
title: 'Recover Password',
user: req.user,
});
};
-export let postForgot = function (req: Request, res: Response, next: NextFunction) {
- const email = req.body.email;
+export const postForgot = function (req: Request, res: Response, next: NextFunction) {
+ const { email } = req.body;
async.waterfall(
[
function (done: any) {
- c.randomBytes(20, function (err: any, buffer: Buffer) {
+ c.randomBytes(20, (err: any, buffer: Buffer) => {
if (err) {
done(null);
- return;
- }
- done(null, buffer.toString('hex'));
+ } else done(null, buffer.toString('hex'));
});
},
function (token: string, done: any) {
@@ -204,20 +204,21 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
'\n\n' +
'If you did not request this, please ignore this email and your password will remain unchanged.\n',
} as MailOptions;
- smtpTransport.sendMail(mailOptions, function (err: Error | null) {
+ smtpTransport.sendMail(mailOptions, (err: Error | null) => {
// req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
done(null, err, 'done');
});
},
],
- function (err) {
+ err => {
if (err) return next(err);
res.redirect('/forgotPassword');
+ return undefined;
}
);
};
-export let getReset = function (req: Request, res: Response) {
+export const getReset = function (req: Request, res: Response) {
User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } })
.then((user: any) => {
if (!user) return res.redirect('/forgotPassword');
@@ -225,11 +226,12 @@ export let getReset = function (req: Request, res: Response) {
title: 'Reset Password',
user: req.user,
});
+ return undefined;
})
- .catch((err: any) => res.redirect('/forgotPassword'));
+ .catch(() => res.redirect('/forgotPassword'));
};
-export let postReset = function (req: Request, res: Response) {
+export const postReset = function (req: Request, res: Response) {
async.waterfall(
[
function (done: any) {
@@ -251,10 +253,11 @@ export let postReset = function (req: Request, res: Response) {
() => (req as any).logIn(user),
(err: any) => err
)
- .catch((err: any) => res.redirect('/login'));
+ .catch(() => res.redirect('/login'));
done(null, user);
+ return undefined;
})
- .catch((err: any) => res.redirect('back'));
+ .catch(() => res.redirect('back'));
},
function (user: DashUserModel, done: any) {
const smtpTransport = nodemailer.createTransport({
@@ -268,13 +271,13 @@ export let postReset = function (req: Request, res: Response) {
to: user.email,
from: 'browndashptc@gmail.com',
subject: 'Your password has been changed',
- text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n',
+ text: 'Hello,\n\nThis is a confirmation that the password for your account ' + user.email + ' has just been changed.\n',
} as MailOptions;
smtpTransport.sendMail(mailOptions, err => done(null, err));
},
],
- function (err) {
+ () => {
res.redirect('/login');
}
);
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index dbb7a79ed..a288bfeab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,9 +1,8 @@
-//@ts-ignore
import * as bcrypt from 'bcrypt-nodejs';
-//@ts-ignore
import * as mongoose from 'mongoose';
import { Utils } from '../../Utils';
+type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void;
export type DashUserModel = mongoose.Document & {
email: String;
password: string;
@@ -26,8 +25,6 @@ export type DashUserModel = mongoose.Document & {
comparePassword: comparePasswordFunction;
};
-type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void;
-
export type AuthToken = {
accessToken: string;
kind: string;
@@ -75,16 +72,19 @@ userSchema.pre('save', function save(next) {
bcrypt.hash(
user.password,
salt,
- () => void {},
- (err: mongoose.Error, hash: string) => {
- if (err) {
- return next(err);
+ () => {},
+ (cryptErr: mongoose.Error, hash: string) => {
+ if (cryptErr) {
+ return next(cryptErr);
}
user.password = hash;
next();
+ return undefined;
}
);
+ return undefined;
});
+ return undefined;
});
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
@@ -97,7 +97,7 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
userSchema.methods.comparePassword = comparePassword;
-const User = mongoose.model('User', userSchema);
+const User: any = mongoose.model('User', userSchema);
export function initializeGuest() {
new User({
email: 'guest',
diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts
index a9cf6698b..a5222e531 100644
--- a/src/server/authentication/Passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -1,6 +1,6 @@
import * as passport from 'passport';
import * as passportLocal from 'passport-local';
-import { DashUserModel, default as User } from './DashUserModel';
+import User, { DashUserModel } from './DashUserModel';
const LocalStrategy = passportLocal.Strategy;
@@ -11,22 +11,25 @@ passport.serializeUser<any, any>((req, user, done) => {
passport.deserializeUser<any, any>((id, done) => {
User.findById(id)
.exec()
- .then(user => done(undefined, user));
+ .then((user: any) => done(undefined, user));
});
// AUTHENTICATE JUST WITH EMAIL AND PASSWORD
passport.use(
new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() })
- .then(user => {
- if (!user) return done(undefined, false, { message: 'Invalid email or password' }); // invalid email
- (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
- if (error) return done(error);
- if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
- // valid authentication HERE
- return done(undefined, user);
- });
+ .then((user: any) => {
+ if (!user) {
+ done(undefined, false, { message: 'Invalid email or password' }); // invalid email
+ } else {
+ (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
+ if (error) return done(error);
+ if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
+ // valid authentication HERE
+ return done(undefined, user);
+ });
+ }
})
- .catch(error => done(error));
+ .catch((error: any) => done(error));
})
);
diff --git a/src/server/database.ts b/src/server/database.ts
index 3a087ce38..ff5635b2c 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -9,6 +9,7 @@ import { Transferable } from './Message';
import { Upload } from './SharedMediaTypes';
export namespace Database {
+ // eslint-disable-next-line import/no-mutable-exports
export let disconnect: Function;
class DocSchema implements mongodb.BSON.Document {
@@ -30,7 +31,10 @@ export namespace Database {
export async function tryInitializeConnection() {
try {
const { connection } = mongoose;
- disconnect = async () => new Promise<any>(resolve => connection.close().then(resolve));
+ disconnect = async () =>
+ new Promise<any>(resolve => {
+ connection.close().then(resolve);
+ });
if (connection.readyState === ConnectionStates.disconnected) {
await new Promise<void>((resolve, reject) => {
connection.on('error', reject);
@@ -39,7 +43,7 @@ export namespace Database {
resolve();
});
mongoose.connect(url, {
- //useNewUrlParser: true,
+ // useNewUrlParser: true,
dbName: schema,
// reconnectTries: Number.MAX_VALUE,
// reconnectInterval: 1000,
@@ -54,6 +58,7 @@ export namespace Database {
}
}
+ // eslint-disable-next-line @typescript-eslint/no-shadow
export class Database implements IDatabase {
private MongoClient = mongodb.MongoClient;
private currentWrites: { [id: string]: Promise<void> } = {};
@@ -81,8 +86,8 @@ export namespace Database {
const collection = this.db.collection<DocSchema>(collectionName);
const prom = this.currentWrites[id];
let newProm: Promise<void>;
- const run = (): Promise<void> => {
- return new Promise<void>(resolve => {
+ const run = (): Promise<void> =>
+ new Promise<void>(resolve => {
collection
.updateOne({ _id: id }, value, { upsert })
.then(res => {
@@ -96,13 +101,12 @@ export namespace Database {
console.log('MOngo UPDATE ONE ERROR:', error);
});
});
- };
newProm = prom ? prom.then(run) : run();
this.currentWrites[id] = newProm;
return newProm;
- } else {
- this.onConnect.push(() => this.update(id, value, callback, upsert, collectionName));
}
+ this.onConnect.push(() => this.update(id, value, callback, upsert, collectionName));
+ return undefined;
}
public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult<mongodb.Document>) => void, upsert = true, collectionName = DocumentsCollection) {
@@ -110,8 +114,8 @@ export namespace Database {
const collection = this.db.collection<DocSchema>(collectionName);
const prom = this.currentWrites[id];
let newProm: Promise<void>;
- const run = (): Promise<void> => {
- return new Promise<void>(resolve => {
+ const run = (): Promise<void> =>
+ new Promise<void>(resolve => {
collection.replaceOne({ _id: id }, value, { upsert }).then(res => {
if (this.currentWrites[id] === newProm) {
delete this.currentWrites[id];
@@ -120,7 +124,6 @@ export namespace Database {
callback(undefined as any, res as any);
});
});
- };
newProm = prom ? prom.then(run) : run();
this.currentWrites[id] = newProm;
} else {
@@ -132,8 +135,10 @@ export namespace Database {
const cursor = this.db?.listCollections();
const collectionNames: string[] = [];
if (cursor) {
+ // eslint-disable-next-line no-await-in-loop
while (await cursor.hasNext()) {
- const collection: any = await cursor.next();
+ // eslint-disable-next-line no-await-in-loop
+ const collection = await cursor.next();
collection && collectionNames.push(collection.name);
}
}
@@ -141,26 +146,30 @@ export namespace Database {
}
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>;
+ // eslint-disable-next-line no-dupe-class-members
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>;
- public delete(id: any, collectionName = DocumentsCollection) {
+ // eslint-disable-next-line no-dupe-class-members
+ public delete(idIn: any, collectionName = DocumentsCollection) {
+ let id = idIn;
if (typeof id === 'string') {
id = { _id: id };
}
if (this.db) {
- const db = this.db;
- return new Promise(res =>
- db
- .collection(collectionName)
+ const { db } = this;
+ return new Promise(res => {
+ db.collection(collectionName)
.deleteMany(id)
- .then(result => res(result))
- );
- } else {
- return new Promise(res => this.onConnect.push(() => res(this.delete(id, collectionName))));
+ .then(result => res(result));
+ });
}
+ return new Promise(res => {
+ this.onConnect.push(() => res(this.delete(id, collectionName)));
+ });
}
public async dropSchema(...targetSchemas: string[]): Promise<any> {
const executor = async (database: mongodb.Db) => {
+ // eslint-disable-next-line no-use-before-define
const existing = await Instance.getCollectionNames();
let valid: string[];
if (targetSchemas.length) {
@@ -173,12 +182,13 @@ export namespace Database {
};
if (this.db) {
return executor(this.db);
- } else {
- this.onConnect.push(() => this.db && executor(this.db));
}
+ this.onConnect.push(() => this.db && executor(this.db));
+ return undefined;
}
- public async insert(value: any, collectionName = DocumentsCollection) {
+ public async insert(valueIn: any, collectionName = DocumentsCollection) {
+ const value = valueIn;
if (this.db && value !== null) {
if ('id' in value) {
value._id = value.id;
@@ -188,36 +198,36 @@ export namespace Database {
const collection = this.db.collection<DocSchema>(collectionName);
const prom = this.currentWrites[id];
let newProm: Promise<void>;
- const run = (): Promise<void> => {
- return new Promise<void>(resolve => {
+ const run = (): Promise<void> =>
+ new Promise<void>(resolve => {
collection
.insertOne(value)
- .then(res => {
+ .then(() => {
if (this.currentWrites[id] === newProm) {
delete this.currentWrites[id];
}
resolve();
})
- .catch(err => {
- console.log('Mongo INSERT ERROR: ', err);
- });
+ .catch(err => console.log('Mongo INSERT ERROR: ', err));
});
- };
newProm = prom ? prom.then(run) : run();
this.currentWrites[id] = newProm;
return newProm;
- } else if (value !== null) {
+ }
+ if (value !== null) {
this.onConnect.push(() => this.insert(value, collectionName));
}
+ return undefined;
}
public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) {
if (this.db) {
const collection = this.db.collection<DocSchema>(collectionName);
- collection.findOne({ _id: id }).then(result => {
+ collection.findOne({ _id: id }).then(resultIn => {
+ const result = resultIn;
if (result) {
result.id = result._id;
- //delete result._id;
+ // delete result._id;
fn(result as any);
} else {
fn(undefined);
@@ -235,7 +245,8 @@ export namespace Database {
.find({ _id: { $in: ids } })
.toArray();
fn(
- found.map((doc: any) => {
+ found.map((docIn: any) => {
+ const doc = docIn;
doc.id = doc._id;
delete doc._id;
return doc;
@@ -253,24 +264,26 @@ export namespace Database {
const count = Math.min(ids.length, 1000);
const index = ids.length - count;
const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
- if (!fetchIds.length) {
- continue;
- }
- const docs = await new Promise<{ [key: string]: any }[]>(res => this.getDocuments(fetchIds, res, collectionName));
- for (const doc of docs) {
- const id = doc.id;
- visited.add(id);
- ids.push(...(await fn(doc)));
+ if (fetchIds.length) {
+ // eslint-disable-next-line no-await-in-loop
+ const docs = await new Promise<{ [key: string]: any }[]>(res => {
+ this.getDocuments(fetchIds, res, collectionName);
+ });
+ docs.forEach(async doc => {
+ const { id } = doc;
+ visited.add(id);
+ ids.push(...(await fn(doc)));
+ });
}
}
- } else {
- return new Promise(res => {
- this.onConnect.push(() => {
- this.visit(ids, fn, collectionName);
- res();
- });
- });
+ return undefined;
}
+ return new Promise(res => {
+ this.onConnect.push(() => {
+ this.visit(ids, fn, collectionName);
+ res();
+ });
+ });
}
public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.FindCursor> {
@@ -280,36 +293,31 @@ export namespace Database {
cursor = cursor.project(projection);
}
return Promise.resolve<mongodb.FindCursor>(cursor);
- } else {
- return new Promise<mongodb.FindCursor>(res => {
- this.onConnect.push(() => res(this.query(query, projection, collectionName)));
- });
}
+ return new Promise<mongodb.FindCursor>(res => {
+ this.onConnect.push(() => {
+ res(this.query(query, projection, collectionName));
+ });
+ });
}
public updateMany(query: any, update: any, collectionName = DocumentsCollection) {
if (this.db) {
- const db = this.db;
- return new Promise<mongodb.UpdateResult>(res =>
- db
- .collection(collectionName)
+ const { db } = this;
+ return new Promise<mongodb.UpdateResult>(res => {
+ db.collection(collectionName)
.updateMany(query, update)
.then(result => res(result))
- .catch(error => {
- console.log('Mongo INSERT MANY ERROR:', error);
- })
- );
- } else {
- return new Promise<mongodb.UpdateResult>(res => {
- this.onConnect.push(() =>
- this.updateMany(query, update, collectionName)
- .then(res)
- .catch(error => {
- console.log('Mongo UPDATAE MANY ERROR: ', error);
- })
- );
+ .catch(error => console.log('Mongo INSERT MANY ERROR:', error));
});
}
+ return new Promise<mongodb.UpdateResult>(res => {
+ this.onConnect.push(() =>
+ this.updateMany(query, update, collectionName)
+ .then(res)
+ .catch(error => console.log('Mongo UPDATAE MANY ERROR: ', error))
+ );
+ });
}
public print() {
@@ -375,9 +383,7 @@ export namespace Database {
* Checks to see if an image with the given @param contentSize
* already exists in the aux database, i.e. has already been downloaded from Google Photos.
*/
- export const QueryUploadHistory = async (contentSize: number) => {
- return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
- };
+ export const QueryUploadHistory = async (contentSize: number) => SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
/**
* Records the uploading of the image with the given @param information,
@@ -405,28 +411,25 @@ export namespace Database {
* Retrieves the credentials associaed with @param userId
* and optionally removes their database id according to @param removeId.
*/
- export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
- return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
- };
+ export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
/**
* Writes the @param enrichedCredentials to the database, associated
* with @param userId for later retrieval and updating.
*/
- export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
- return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
- };
+ export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
/**
- * Updates the @param access_token and @param expiry_date fields
+ * Updates the @param accessToken and @param expiryDate fields
* in the stored credentials associated with @param userId.
*/
- export const Update = async (userId: string, access_token: string, expiry_date: number) => {
+ export const Update = async (userId: string, accessToken: string, expiryDate: number) => {
const entry = await Fetch(userId, false);
if (entry) {
- const parameters = { $set: { access_token, expiry_date } };
+ const parameters = { $set: { access_token: accessToken, expiry_date: expiryDate } };
return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAccess);
}
+ return undefined;
};
/**
diff --git a/src/server/index.ts b/src/server/index.ts
index 47c37c9f0..4374a72b7 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,14 +1,14 @@
-import * as dotenv from 'dotenv';
import { yellow } from 'colors';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import * as dotenv from 'dotenv';
import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
-import * as qs from 'query-string';
-import { log_execution } from './ActionUtilities';
+import { logExecution } from './ActionUtilities';
+import { AdminPrivileges, resolvedPorts } from './SocketData';
import DataVizManager from './ApiManagers/DataVizManager';
import DeleteManager from './ApiManagers/DeleteManager';
import DownloadManager from './ApiManagers/DownloadManager';
import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
-//import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
import { SearchManager } from './ApiManagers/SearchManager';
import SessionManager from './ApiManagers/SessionManager';
import UploadManager from './ApiManagers/UploadManager';
@@ -24,13 +24,13 @@ import { Database } from './database';
import { Logger } from './ProcessFactory';
import RouteManager, { Method, PublicHandler } from './RouteManager';
import RouteSubscriber from './RouteSubscriber';
-import initializeServer, { resolvedPorts } from './server_Initialization';
+import initializeServer from './server_Initialization';
+// import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+
dotenv.config();
-export const AdminPriviliges: Map<string, boolean> = new Map();
export const onWindows = process.platform === 'win32';
+// eslint-disable-next-line import/no-mutable-exports
export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, 'public');
-export const filesDirectory = path.resolve(publicDirectory, 'files');
/**
* These are the functions run before the server starts
@@ -45,7 +45,7 @@ async function preliminaryFunctions() {
SSL.loadCredentials();
GoogleApiServerUtils.processProjectCredentials();
if (process.env.DB !== 'MEM') {
- await log_execution({
+ await logExecution({
startMessage: 'attempting to initialize mongodb connection',
endMessage: 'connection outcome determined',
action: Database.tryInitializeConnection,
@@ -62,8 +62,18 @@ async function preliminaryFunctions() {
* that will manage the registration of new routes
* with the server
*/
-function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
- const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(),*/ new DataVizManager()];
+function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
+ const managers = [
+ new SessionManager(),
+ new UserManager(),
+ new UploadManager(),
+ new DownloadManager(),
+ new SearchManager(),
+ new DeleteManager(),
+ new UtilManager(),
+ new GeneralGoogleManager(),
+ /* new GooglePhotosManager(), */ new DataVizManager(),
+ ];
// initialize API Managers
console.log(yellow('\nregistering server routes...'));
@@ -104,6 +114,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
});
const serve: PublicHandler = ({ req, res }) => {
+ // eslint-disable-next-line new-cap
const detector = new mobileDetect(req.headers['user-agent'] || '');
const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
res.sendFile(path.join(__dirname, '../../deploy/' + filename));
@@ -118,9 +129,8 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: ({ res, isRelease }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect('/home');
- }
- res.render('admin.pug', { title: 'Enter Administrator Password' });
+ res.redirect('/home');
+ } else res.render('admin.pug', { title: 'Enter Administrator Password' });
},
});
@@ -130,18 +140,19 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: async ({ req, res, isRelease, user: { id } }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect('/home');
- }
- const { password } = req.body;
- const { previous_target } = req.params;
- let redirect: string;
- if (password === PASSWORD) {
- AdminPriviliges.set(id, true);
- redirect = `/${previous_target.replace(':', '/')}`;
+ res.redirect('/home');
} else {
- redirect = `/admin/${previous_target}`;
+ const { password } = req.body;
+ const { previous_target: previousTarget } = req.params;
+ let redirect: string;
+ if (password === PASSWORD) {
+ AdminPrivileges.set(id, true);
+ redirect = `/${previousTarget.replace(':', '/')}`;
+ } else {
+ redirect = `/admin/${previousTarget}`;
+ }
+ res.redirect(redirect);
}
- res.redirect(redirect);
},
});
@@ -151,7 +162,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: serve,
publicHandler: ({ req, res, ...remaining }) => {
const { originalUrl: target } = req;
- const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
const docAccess = target.startsWith('/doc/');
// since this is the public handler, there's no meaning of '/home' to speak of
// since there's no user logged in, so the only viable operation
@@ -174,7 +184,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
* the main monitor (master) thread.
*/
export async function launchServer() {
- await log_execution({
+ await logExecution({
startMessage: '\nstarting execution of preliminary functions',
endMessage: 'completed preliminary functions\n',
action: preliminaryFunctions,
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index b8e17ec66..ca7ca241f 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,58 +1,69 @@
-import { Database } from "./database";
-import { resolvedPorts } from "./server_Initialization";
+import { URL } from 'url';
+import { Database } from './database';
+import { resolvedPorts } from './SocketData';
-//npx ts-node src/server/remapUrl.ts
+// npx ts-node src/server/remapUrl.ts
const suffixMap: { [type: string]: true } = {
- "video": true,
- "pdf": true,
- "audio": true,
- "web": true,
- "image": true,
- "map": true,
+ video: true,
+ pdf: true,
+ audio: true,
+ web: true,
+ image: true,
+ map: true,
};
async function update() {
- await new Promise(res => setTimeout(res, 10));
- console.log("update");
+ await new Promise(res => {
+ setTimeout(res, 10);
+ });
+ console.log('update');
const cursor = await Database.Instance.query({});
- console.log("Cleared");
+ console.log('Cleared');
const updates: [string, any][] = [];
function updateDoc(doc: any) {
- if (doc.__type !== "Doc") {
+ if (doc.__type !== 'Doc') {
return;
}
- const fields = doc.fields;
+ const { fields } = doc;
if (!fields) {
return;
}
- const update: any = {
- };
+ const updated: any = {};
let dynfield = false;
- for (const key in fields) {
+ Array.from(Object.keys(fields)).forEach(key => {
const value = fields[key];
if (value && value.__type && suffixMap[value.__type]) {
const url = new URL(value.url);
- if (url.href.includes("localhost") && url.href.includes("Bill")) {
+ if (url.href.includes('localhost') && url.href.includes('Bill')) {
dynfield = true;
- update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
+ updated.$set = { ['fields.' + key + '.url']: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
}
}
- }
+ });
if (dynfield) {
- updates.push([doc._id, update]);
+ updates.push([doc._id, updated]);
}
}
await cursor.forEach(updateDoc);
- await Promise.all(updates.map(doc => {
- console.log(doc[0], doc[1]);
- return new Promise<void>(res => Database.Instance.update(doc[0], doc[1], () => {
- console.log("wrote " + JSON.stringify(doc[1]));
- res();
- }, false));
- }));
- console.log("Done");
+ await Promise.all(
+ updates.map(doc => {
+ console.log(doc[0], doc[1]);
+ return new Promise<void>(res => {
+ Database.Instance.update(
+ doc[0],
+ doc[1],
+ () => {
+ console.log('wrote ' + JSON.stringify(doc[1]));
+ res();
+ },
+ false
+ );
+ });
+ })
+ );
+ console.log('Done');
// await Promise.all(updates.map(update => {
// return limit(() => Search.updateDocument(update));
// }));
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 2d52ea906..8db7e9933 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,73 +1,59 @@
import * as bodyParser from 'body-parser';
+import * as brotli from 'brotli';
import { blue, yellow } from 'colors';
+import * as flash from 'connect-flash';
+import * as MongoStoreConnect from 'connect-mongo';
import * as cors from 'cors';
import * as express from 'express';
+import * as expressFlash from 'express-flash';
import * as session from 'express-session';
import { createServer } from 'https';
import * as passport from 'passport';
import * as request from 'request';
import * as webpack from 'webpack';
import * as wdm from 'webpack-dev-middleware';
+// eslint-disable-next-line import/no-extraneous-dependencies
import * as whm from 'webpack-hot-middleware';
import * as zlib from 'zlib';
-import { publicDirectory } from '.';
+import * as config from '../../webpack.config.js';
import { logPort } from './ActionUtilities';
+import RouteManager from './RouteManager';
+import RouteSubscriber from './RouteSubscriber';
+import { publicDirectory, resolvedPorts } from './SocketData';
import { SSL } from './apis/google/CredentialsLoader';
import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
import { Database } from './database';
-import RouteManager from './RouteManager';
-import RouteSubscriber from './RouteSubscriber';
import { WebSocket } from './websocket';
-import * as expressFlash from 'express-flash';
-import * as flash from 'connect-flash';
-import * as brotli from 'brotli';
-import * as MongoStoreConnect from 'connect-mongo';
-import * as config from '../../webpack.config';
/* RouteSetter is a wrapper around the server that prevents the server
from being exposed. */
export type RouteSetter = (server: RouteManager) => void;
-//export let disconnect: Function;
+// export let disconnect: Function;
-export let resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 };
+// eslint-disable-next-line import/no-mutable-exports
export let resolvedServerUrl: string;
-export default async function InitializeServer(routeSetter: RouteSetter) {
- const isRelease = determineEnvironment();
- const app = buildWithMiddleware(express());
- const compiler = webpack(config as any);
-
- // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request
- app.use(wdm(compiler, { publicPath: config.output.publicPath }));
- app.use(whm(compiler));
- app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
- app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); //all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc)
- app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
- registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
- registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
- isRelease && !SSL.Loaded && SSL.exit();
- routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
- registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
+const week = 7 * 24 * 60 * 60 * 1000;
+const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc';
+const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url });
- isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
- const server = isRelease ? createServer(SSL.Credentials, app) : app;
- await new Promise<void>(resolve => server.listen(resolvedPorts.server, resolve));
- logPort('server', resolvedPorts.server);
+/* Determine if the enviroment is dev mode or release mode. */
+function determineEnvironment() {
+ const isRelease = process.env.RELEASE === 'true';
- resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`;
+ const color = isRelease ? blue : yellow;
+ const label = isRelease ? 'release' : 'development';
+ console.log(`\nrunning server in ${color(label)} mode`);
- // initialize the web socket (bidirectional communication: if a user changes
- // a field on one client, that change must be broadcast to all other clients)
- await WebSocket.initialize(isRelease, SSL.Credentials);
+ // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
+ // // on the client side, thanks to dotenv in webpack.config.js
+ // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
+ // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
+ // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
- //disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
return isRelease;
}
-const week = 7 * 24 * 60 * 60 * 1000;
-const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc';
-const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url });
-
function buildWithMiddleware(server: express.Express) {
[
session({
@@ -100,72 +86,43 @@ function buildWithMiddleware(server: express.Express) {
return server;
}
-/* Determine if the enviroment is dev mode or release mode. */
-function determineEnvironment() {
- const isRelease = process.env.RELEASE === 'true';
-
- const color = isRelease ? blue : yellow;
- const label = isRelease ? 'release' : 'development';
- console.log(`\nrunning server in ${color(label)} mode`);
-
- // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
- // // on the client side, thanks to dotenv in webpack.config.js
- // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
- // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
- // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
-
- return isRelease;
-}
-
-function registerAuthenticationRoutes(server: express.Express) {
- server.get('/signup', getSignup);
- server.post('/signup', postSignup);
-
- server.get('/login', getLogin);
- server.post('/login', postLogin);
-
- server.get('/logout', getLogout);
-
- server.get('/forgotPassword', getForgot);
- server.post('/forgotPassword', postForgot);
-
- const reset = new RouteSubscriber('resetPassword').add('token').build;
- server.get(reset, getReset);
- server.post(reset, postReset);
-}
-
-function registerCorsProxy(server: express.Express) {
- server.use('/corsProxy', async (req, res) => {
- res.setHeader('Access-Control-Allow-Origin', '*');
- res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
- res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
- 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=');
- 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);
+function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
+ server.use('*', (req, res) => {
+ // res.setHeader('Access-Control-Allow-Origin', '*');
+ // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+ // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+ const relativeUrl = req.originalUrl;
+ 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)
+ const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
+ const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
+ const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
+ const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
+ const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
+ res.redirect(redirectUrl);
+ } catch (e) {
+ console.log('Error embed: ', e);
+ }
+ } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) {
+ // detect search query and use default search engine
+ res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl));
} else {
- proxyServe(req, requrl, res);
+ res.end();
}
});
}
function proxyServe(req: any, requrl: string, response: any) {
+ // eslint-disable-next-line global-require
const htmlBodyMemoryStream = new (require('memorystream'))();
- var wasinBrFormat = false;
+ let wasinBrFormat = false;
const sendModifiedBody = () => {
const header = response.headers['content-encoding'];
- const refToCors = (match: any, tag: string, sym: string, href: string, offset: any, string: any) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
- const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
+ const refToCors = (match: any, tag: string, sym: string, href: string) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
+ // const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
if (header) {
try {
const bodyStream = htmlBodyMemoryStream.read();
@@ -174,8 +131,8 @@ function proxyServe(req: any, requrl: string, response: any) {
const htmlText = htmlInputText
.toString('utf8')
.replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- .replace(/(src|href)=([\'\"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
- //.replace(/= *"\/([^"]*)"/g, relpathToCors)
+ .replace(/(src|href)=(['"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
+ // .replace(/= *"\/([^"]*)"/g, relpathToCors)
.replace(/data-srcset="[^"]*"/g, '')
.replace(/srcset="[^"]*"/g, '')
.replace(/target="_blank"/g, '');
@@ -198,7 +155,7 @@ function proxyServe(req: any, requrl: string, response: any) {
}
};
const retrieveHTTPBody = () => {
- //req.headers.cookie = '';
+ // req.headers.cookie = '';
req.pipe(request(requrl))
.on('error', (e: any) => {
console.log(`CORS url error: ${requrl}`, e);
@@ -227,6 +184,7 @@ function proxyServe(req: any, requrl: string, response: any) {
res.headers['x-permitted-cross-domain-policies'] = 'all';
res.headers['x-frame-options'] = '';
res.headers['content-security-policy'] = '';
+ // eslint-disable-next-line no-multi-assign
response.headers = response._headers = res.headers;
})
.on('end', sendModifiedBody)
@@ -236,31 +194,78 @@ function proxyServe(req: any, requrl: string, response: any) {
retrieveHTTPBody();
}
-function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
- server.use('*', (req, res) => {
- // res.setHeader('Access-Control-Allow-Origin', '*');
- // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
- // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
- const relativeUrl = req.originalUrl;
- 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)
- const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
- const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
- const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
- const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
- res.redirect(redirectUrl);
- } catch (e) {
- console.log('Error embed: ', e);
- }
- } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) {
- // detect search query and use default search engine
- res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl));
+function registerCorsProxy(server: express.Express) {
+ server.use('/corsProxy', async (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+ res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+ 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=');
+ 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 {
- res.end();
+ proxyServe(req, requrl, res);
}
});
}
+
+function registerAuthenticationRoutes(server: express.Express) {
+ server.get('/signup', getSignup);
+ server.post('/signup', postSignup);
+
+ server.get('/login', getLogin);
+ server.post('/login', postLogin);
+
+ server.get('/logout', getLogout);
+
+ server.get('/forgotPassword', getForgot);
+ server.post('/forgotPassword', postForgot);
+
+ const reset = new RouteSubscriber('resetPassword').add('token').build;
+ server.get(reset, getReset);
+ server.post(reset, postReset);
+}
+
+export default async function InitializeServer(routeSetter: RouteSetter) {
+ const isRelease = determineEnvironment();
+ const app = buildWithMiddleware(express());
+ const compiler = webpack(config as any);
+
+ // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request
+ app.use(wdm(compiler, { publicPath: config.output.publicPath }));
+ app.use(whm(compiler));
+ app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
+ app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc)
+ app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
+ registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
+ registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
+ isRelease && !SSL.Loaded && SSL.exit();
+ routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
+ registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
+
+ isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
+ const server = isRelease ? createServer(SSL.Credentials, app) : app;
+ await new Promise<void>(resolve => {
+ server.listen(resolvedPorts.server, resolve);
+ });
+ logPort('server', resolvedPorts.server);
+
+ resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`;
+
+ // initialize the web socket (bidirectional communication: if a user changes
+ // a field on one client, that change must be broadcast to all other clients)
+ await WebSocket.initialize(isRelease, SSL.Credentials);
+
+ // disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
+ return isRelease;
+}
diff --git a/src/server/updateProtos.ts b/src/server/updateProtos.ts
index 2f3772a77..72a44ebf4 100644
--- a/src/server/updateProtos.ts
+++ b/src/server/updateProtos.ts
@@ -6,15 +6,15 @@ const protos = ['text', 'image', 'web', 'collection', 'kvp', 'video', 'audio', '
await Promise.all(
protos.map(
protoId =>
- new Promise(res =>
+ new Promise(res => {
Database.Instance.update(
protoId,
{
$set: { 'fields.isBaseProto': true },
},
res
- )
- )
+ );
+ })
)
);
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 38134f2c1..cece8a1b7 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -3,152 +3,26 @@ import { createServer } from 'https';
import * as _ from 'lodash';
import { networkInterfaces } from 'os';
import { Server, Socket } from 'socket.io';
-import { Utils } from '../Utils';
+import { ServerUtils } from '../ServerUtils';
import { logPort } from './ActionUtilities';
-import { timeMap } from './ApiManagers/UserManager';
import { Client } from './Client';
import { DashStats } from './DashStats';
import { DocumentsCollection } from './IDatabase';
import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
import { Search } from './Search';
+import { resolvedPorts, socketMap, timeMap, userOperations } from './SocketData';
import { GoogleCredentialsLoader } from './apis/google/CredentialsLoader';
import YoutubeApi from './apis/youtube/youtubeApiSample';
import { initializeGuest } from './authentication/DashUserModel';
import { Database } from './database';
-import { resolvedPorts } from './server_Initialization';
export namespace WebSocket {
+ let CurUser: string | undefined;
+ // eslint-disable-next-line import/no-mutable-exports
export let _socket: Socket;
+ // eslint-disable-next-line import/no-mutable-exports
+ export let _disconnect: Function;
export const clients: { [key: string]: Client } = {};
- export const socketMap = new Map<Socket, string>();
- export const userOperations = new Map<string, number>();
- export let disconnect: Function;
-
- export async function initialize(isRelease: boolean, credentials:any) {
- let io: Server;
- if (isRelease) {
- const { socketPort } = process.env;
- if (socketPort) {
- resolvedPorts.socket = Number(socketPort);
- }
- const httpsServer = createServer(credentials);
- io = new Server(httpsServer, {})
- httpsServer.listen(resolvedPorts.socket);
- } else {
- io = new Server();
- io.listen(resolvedPorts.socket);
- }
- logPort('websocket', resolvedPorts.socket);
-
- io.on('connection', socket => {
- _socket = socket;
- socket.use((_packet, next) => {
- const userEmail = socketMap.get(socket);
- if (userEmail) {
- timeMap[userEmail] = Date.now();
- }
- next();
- });
-
- socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
-
- // convenience function to log server messages on the client
- function log(message?: any, ...optionalParams: any[]) {
- socket.emit('log', ['Message from server:', message, ...optionalParams]);
- }
-
- socket.on('message', function (message, room) {
- console.log('Client said: ', message);
- socket.in(room).emit('message', message);
- });
-
- socket.on('create or join', function (room) {
- console.log('Received request to create or join room ' + room);
-
- const clientsInRoom = socket.rooms.has(room);
- const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0;
- console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
-
- if (numClients === 0) {
- socket.join(room);
- console.log('Client ID ' + socket.id + ' created room ' + room);
- socket.emit('created', room, socket.id);
- } else if (numClients === 1) {
- console.log('Client ID ' + socket.id + ' joined room ' + room);
- socket.in(room).emit('join', room);
- socket.join(room);
- socket.emit('joined', room, socket.id);
- socket.in(room).emit('ready');
- } else {
- // max two clients
- socket.emit('full', room);
- }
- });
-
- socket.on('ipaddr', function () {
- const ifaces = networkInterfaces();
- for (const dev in ifaces) {
- ifaces[dev]?.forEach(function (details) {
- if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
- socket.emit('ipaddr', details.address);
- }
- });
- }
- });
-
- socket.on('bye', function () {
- console.log('received bye');
- });
-
- socket.on('disconnect', function () {
- let currentUser = socketMap.get(socket);
- if (!(currentUser === undefined)) {
- let currentUsername = currentUser.split(' ')[0];
- DashStats.logUserLogout(currentUsername, socket);
- delete timeMap[currentUsername];
- }
- });
-
- Utils.Emit(socket, MessageStore.Foo, 'handshooken');
-
- Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
- Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
- Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
- Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
- if (isRelease) {
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
- }
-
- Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
- Utils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery);
- Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
- Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
- Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
- Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
- Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
- Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
- Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
- Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
- Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
-
- /**
- * Whenever we receive the go-ahead, invoke the import script and pass in
- * as an emitter and a terminator the functions that simply broadcast a result
- * or indicate termination to the client via the web socket
- */
-
- disconnect = () => {
- socket.broadcast.emit('connection_terminated', Date.now());
- socket.disconnect(true);
- };
- });
-
- setInterval(function () {
- // Utils.Emit(socket, MessageStore.UpdateStats, DashStats.getUpdatedStatsBundle());
-
- io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
- }, DashStats.SAMPLING_INTERVAL);
- }
function processGesturePoints(socket: Socket, content: GestureContent) {
socket.broadcast.emit('receiveGesturePoints', content);
@@ -174,8 +48,11 @@ export namespace WebSocket {
break;
case YoutubeQueryTypes.SearchVideo:
YoutubeApi.authorizedGetVideos(ProjectCredentials, query.userInput, callback);
+ break;
case YoutubeQueryTypes.VideoDetails:
YoutubeApi.authorizedGetVideoDetails(ProjectCredentials, query.videoIds, callback);
+ break;
+ default:
}
}
@@ -189,6 +66,9 @@ export namespace WebSocket {
initializeGuest();
}
+ function printActiveUsers() {
+ socketMap.forEach((user, socket) => !socket.disconnected && console.log(user));
+ }
function barReceived(socket: Socket, userEmail: string) {
clients[userEmail] = new Client(userEmail.toString());
const currentdate = new Date();
@@ -199,11 +79,11 @@ export namespace WebSocket {
timeMap[userEmail] = Date.now();
socketMap.set(socket, userEmail + ' at ' + datetime);
userOperations.set(userEmail, 0);
- DashStats.logUserLogin(userEmail, socket);
+ DashStats.logUserLogin(userEmail);
}
function getField([id, callback]: [string, (result?: Transferable) => void]) {
- Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
+ Database.Instance.getDocument(id, (result?: Transferable) => callback(result));
}
function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -248,27 +128,24 @@ export namespace WebSocket {
list: [
'_l',
list => {
- const results = [];
- for (const value of list.fields) {
- const term = ToSearchTerm(value);
- if (term) {
- results.push(term.value);
- }
- }
+ const results: any[] = [];
+ // eslint-disable-next-line no-use-before-define
+ list.fields.forEach((value: any) => ToSearchTerm(value) && results.push(ToSearchTerm(value)!.value));
return results.length ? results : null;
},
],
};
- function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
+ function ToSearchTerm(valIn: any): { suffix: string; value: any } | undefined {
+ let val = valIn;
if (val === null || val === undefined) {
- return;
+ return undefined;
}
const type = val.__type || typeof val;
let suffix = suffixMap[type];
if (!suffix) {
- return;
+ return undefined;
}
if (Array.isArray(suffix)) {
const accessor = suffix[1];
@@ -277,7 +154,7 @@ export namespace WebSocket {
} else {
val = val[accessor];
}
- suffix = suffix[0];
+ [suffix] = suffix;
}
return { suffix, value: val };
}
@@ -285,8 +162,28 @@ export namespace WebSocket {
function getSuffix(value: string | [string, any]): string {
return typeof value === 'string' ? value : value[0];
}
+ const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
+
+ function dispatchNextOp(id: string) {
+ const next = pendingOps.get(id)!.shift();
+ if (next) {
+ const { diff, socket } = next;
+ if (diff.diff.$addToSet) {
+ // eslint-disable-next-line no-use-before-define
+ return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ }
+ if (diff.diff.$remFromSet) {
+ // eslint-disable-next-line no-use-before-define
+ return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ }
+ // eslint-disable-next-line no-use-before-define
+ return SetField(socket, diff);
+ }
+ return !pendingOps.get(id)!.length && pendingOps.delete(id);
+ }
- function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+ function addToListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void {
+ const diff = diffIn;
diff.diff.$set = diff.diff.$addToSet;
delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
@@ -296,7 +193,7 @@ export namespace WebSocket {
return;
}
const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
- diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+ diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; // , ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
Database.Instance.update(
@@ -305,11 +202,13 @@ export namespace WebSocket {
() => {
if (sendBack) {
console.log('Warning: list modified during update. Composite list is being returned.');
- const id = socket.id;
- (socket as any).id = '';
+ const { id } = socket;
+ (socket as any).id = ''; // bcz: HACK. this prevents the update message from going back to the client that made the change.
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
(socket as any).id = id;
- } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ } else {
+ socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ }
dispatchNextOp(diff.id);
},
false
@@ -352,28 +251,28 @@ export namespace WebSocket {
* items to delete)
* @param curListItems the server's current copy of the data
*/
- function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+ function remFromListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void {
+ const diff = diffIn;
diff.diff.$set = diff.diff.$remFromSet;
delete diff.diff.$remFromSet;
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
const remListItems = diff.diff.$set[updatefield].fields;
const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
- const hint = diff.diff.$set.hint;
+ const { hint } = diff.diff.$set;
if (hint) {
// indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements)
- let indexesToRemove: number[] = [];
+ const indexesToRemove: number[] = [];
for (let i = 0; i < hint.deleteCount; i++) {
if (curList.length > i + hint.start && _.isEqual(curList[i + hint.start], remListItems[i])) {
indexesToRemove.push(i + hint.start);
- continue;
- }
-
- let closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start);
- if (closestIndex !== -1) {
- indexesToRemove.push(closestIndex);
} else {
- console.log('Item to delete was not found - index = -1');
+ const closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start);
+ if (closestIndex !== -1) {
+ indexesToRemove.push(closestIndex);
+ } else {
+ console.log('Item to delete was not found - index = -1');
+ }
}
}
@@ -398,45 +297,23 @@ export namespace WebSocket {
if (sendBack) {
// the two copies are different, so the server sends its copy.
console.log('SEND BACK');
- const id = socket.id;
- (socket as any).id = '';
+ const { id } = socket;
+ (socket as any).id = ''; // bcz: HACK. this prevents the update message from going back to the client that made the change.
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
(socket as any).id = id;
- } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ } else {
+ socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ }
dispatchNextOp(diff.id);
},
false
);
}
- const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
-
- function dispatchNextOp(id: string) {
- const next = pendingOps.get(id)!.shift();
- if (next) {
- const { diff, socket } = next;
- if (diff.diff.$addToSet) {
- return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
- }
- if (diff.diff.$remFromSet) {
- return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
- }
- return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
- }
- if (!pendingOps.get(id)!.length) pendingOps.delete(id);
- }
-
- function printActiveUsers() {
- socketMap.forEach((user, socket) => {
- !socket.disconnected && console.log(user);
- });
- }
- var CurUser: string | undefined = undefined;
-
function UpdateField(socket: Socket, diff: Diff) {
const curUser = socketMap.get(socket);
- if (!curUser) return;
- let currentUsername = curUser.split(' ')[0];
+ if (!curUser) return false;
+ const currentUsername = curUser.split(' ')[0];
userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
if (CurUser !== socketMap.get(socket)) {
@@ -454,15 +331,18 @@ export namespace WebSocket {
if (diff.diff.$remFromSet) {
return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
}
- return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
+ // eslint-disable-next-line no-use-before-define
+ return SetField(socket, diff);
}
- function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
+ function SetField(socket: Socket, diff: Diff /* , curListItems?: Transferable */) {
Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;
if (docfield) {
const update: any = { id: diff.id };
let dynfield = false;
+ // eslint-disable-next-line no-restricted-syntax
for (let key in docfield) {
+ // eslint-disable-next-line no-continue
if (!key.startsWith('fields.')) continue;
dynfield = true;
const val = docfield[key];
@@ -504,4 +384,98 @@ export namespace WebSocket {
function CreateField(newValue: any) {
Database.Instance.insert(newValue);
}
+ export async function initialize(isRelease: boolean, credentials: any) {
+ let io: Server;
+ if (isRelease) {
+ const { socketPort } = process.env;
+ if (socketPort) {
+ resolvedPorts.socket = Number(socketPort);
+ }
+ const httpsServer = createServer(credentials);
+ io = new Server(httpsServer, {});
+ httpsServer.listen(resolvedPorts.socket);
+ } else {
+ io = new Server();
+ io.listen(resolvedPorts.socket);
+ }
+ logPort('websocket', resolvedPorts.socket);
+
+ io.on('connection', socket => {
+ _socket = socket;
+ socket.use((_packet, next) => {
+ const userEmail = socketMap.get(socket);
+ if (userEmail) {
+ timeMap[userEmail] = Date.now();
+ }
+ next();
+ });
+
+ socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
+
+ socket.on('message', (message, room) => {
+ console.log('Client said: ', message);
+ socket.in(room).emit('message', message);
+ });
+
+ socket.on('ipaddr', () => {
+ networkInterfaces().keys?.forEach(dev => {
+ if (dev.family === 'IPv4' && dev.address !== '127.0.0.1') {
+ socket.emit('ipaddr', dev.address);
+ }
+ });
+ });
+
+ socket.on('bye', () => {
+ console.log('received bye');
+ });
+
+ socket.on('disconnect', () => {
+ const currentUser = socketMap.get(socket);
+ if (!(currentUser === undefined)) {
+ const currentUsername = currentUser.split(' ')[0];
+ DashStats.logUserLogout(currentUsername);
+ delete timeMap[currentUsername];
+ }
+ });
+
+ ServerUtils.Emit(socket, MessageStore.Foo, 'handshooken');
+
+ ServerUtils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
+ ServerUtils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
+ ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+ ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+ if (isRelease) {
+ ServerUtils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
+ }
+
+ ServerUtils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+ ServerUtils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery);
+ ServerUtils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+ ServerUtils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
+ ServerUtils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
+ ServerUtils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
+ ServerUtils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
+ ServerUtils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
+ ServerUtils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
+ ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
+ ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+ /**
+ * Whenever we receive the go-ahead, invoke the import script and pass in
+ * as an emitter and a terminator the functions that simply broadcast a result
+ * or indicate termination to the client via the web socket
+ */
+
+ _disconnect = () => {
+ socket.broadcast.emit('connection_terminated', Date.now());
+ socket.disconnect(true);
+ };
+ });
+
+ setInterval(() => {
+ // Utils.Emit(socket, MessageStore.UpdateStats, DashStats.getUpdatedStatsBundle());
+
+ io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
+ }, DashStats.SAMPLING_INTERVAL);
+ }
}