aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/DocServer.ts44
-rw-r--r--src/client/Network.ts2
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts4
-rw-r--r--src/client/apis/gpt/GPT.ts22
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx2
-rw-r--r--src/client/documents/DocumentTypes.ts4
-rw-r--r--src/client/documents/Documents.ts124
-rw-r--r--src/client/util/BranchingTrailManager.tsx12
-rw-r--r--src/client/util/CalendarManager.scss62
-rw-r--r--src/client/util/CalendarManager.tsx358
-rw-r--r--src/client/util/CaptureManager.scss7
-rw-r--r--src/client/util/CaptureManager.tsx5
-rw-r--r--src/client/util/CurrentUserUtils.ts127
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DocumentManager.ts100
-rw-r--r--src/client/util/DragManager.ts55
-rw-r--r--src/client/util/GroupManager.tsx13
-rw-r--r--src/client/util/History.ts2
-rw-r--r--src/client/util/HypothesisUtils.ts2
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx819
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx84
-rw-r--r--src/client/util/InteractionUtils.tsx2
-rw-r--r--src/client/util/LinkManager.ts26
-rw-r--r--src/client/util/PingManager.ts3
-rw-r--r--src/client/util/RTFMarkup.tsx5
-rw-r--r--src/client/util/ReplayMovements.ts3
-rw-r--r--src/client/util/Scripting.ts11
-rw-r--r--src/client/util/ScrollBox.tsx2
-rw-r--r--src/client/util/SearchUtil.ts6
-rw-r--r--src/client/util/SelectionManager.ts128
-rw-r--r--src/client/util/SerializationHelper.ts1
-rw-r--r--src/client/util/ServerStats.tsx9
-rw-r--r--src/client/util/SettingsManager.scss4
-rw-r--r--src/client/util/SettingsManager.tsx8
-rw-r--r--src/client/util/SharingManager.tsx17
-rw-r--r--src/client/util/SnappingManager.ts95
-rw-r--r--src/client/util/TrackMovements.ts4
-rw-r--r--src/client/util/reportManager/ReportManager.scss7
-rw-r--r--src/client/util/reportManager/ReportManager.tsx11
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx2
-rw-r--r--src/client/views/AntimodeMenu.scss16
-rw-r--r--src/client/views/AntimodeMenu.tsx22
-rw-r--r--src/client/views/AudioWaveform.tsx109
-rw-r--r--src/client/views/ComponentDecorations.tsx14
-rw-r--r--src/client/views/ContextMenu.scss2
-rw-r--r--src/client/views/ContextMenu.tsx101
-rw-r--r--src/client/views/ContextMenuItem.tsx17
-rw-r--r--src/client/views/DashboardView.scss52
-rw-r--r--src/client/views/DashboardView.tsx44
-rw-r--r--src/client/views/DictationOverlay.tsx3
-rw-r--r--src/client/views/DocComponent.tsx97
-rw-r--r--src/client/views/DocumentButtonBar.scss2
-rw-r--r--src/client/views/DocumentButtonBar.tsx258
-rw-r--r--src/client/views/DocumentDecorations.scss2
-rw-r--r--src/client/views/DocumentDecorations.tsx257
-rw-r--r--src/client/views/EditableView.tsx116
-rw-r--r--src/client/views/FilterPanel.tsx10
-rw-r--r--src/client/views/GestureOverlay.tsx24
-rw-r--r--src/client/views/GlobalKeyHandler.ts54
-rw-r--r--src/client/views/InkControlPtHandles.tsx6
-rw-r--r--src/client/views/InkStrokeProperties.ts31
-rw-r--r--src/client/views/InkTangentHandles.tsx2
-rw-r--r--src/client/views/InkTranscription.tsx700
-rw-r--r--src/client/views/InkingStroke.tsx36
-rw-r--r--src/client/views/KeyphraseQueryView.tsx22
-rw-r--r--src/client/views/LightboxView.tsx37
-rw-r--r--src/client/views/Main.scss2
-rw-r--r--src/client/views/Main.tsx5
-rw-r--r--src/client/views/MainView.scss2
-rw-r--r--src/client/views/MainView.tsx122
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/MarqueeAnnotator.tsx47
-rw-r--r--src/client/views/MetadataEntryMenu.tsx1
-rw-r--r--src/client/views/ObservableReactComponent.tsx21
-rw-r--r--src/client/views/OverlayView.scss1
-rw-r--r--src/client/views/OverlayView.tsx45
-rw-r--r--src/client/views/Palette.tsx69
-rw-r--r--src/client/views/PreviewCursor.tsx148
-rw-r--r--src/client/views/PropertiesButtons.scss45
-rw-r--r--src/client/views/PropertiesButtons.tsx43
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx4
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx26
-rw-r--r--src/client/views/PropertiesSection.scss7
-rw-r--r--src/client/views/PropertiesSection.tsx2
-rw-r--r--src/client/views/PropertiesView.scss2
-rw-r--r--src/client/views/PropertiesView.tsx133
-rw-r--r--src/client/views/ScriptBox.tsx3
-rw-r--r--src/client/views/ScriptingRepl.tsx89
-rw-r--r--src/client/views/SidebarAnnos.tsx88
-rw-r--r--src/client/views/StyleProvider.tsx50
-rw-r--r--src/client/views/TemplateMenu.scss7
-rw-r--r--src/client/views/TemplateMenu.tsx3
-rw-r--r--src/client/views/TouchScrollableMenu.tsx34
-rw-r--r--src/client/views/_nodeModuleOverrides.scss2
-rw-r--r--src/client/views/animationtimeline/Region.scss2
-rw-r--r--src/client/views/animationtimeline/Region.tsx62
-rw-r--r--src/client/views/animationtimeline/Timeline.scss18
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx62
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.scss78
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.tsx5
-rw-r--r--src/client/views/animationtimeline/TimelineOverview.scss6
-rw-r--r--src/client/views/animationtimeline/Track.scss5
-rw-r--r--src/client/views/animationtimeline/Track.tsx68
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx107
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.scss7
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx20
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx47
-rw-r--r--src/client/views/collections/CollectionDockingView.scss21
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx92
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx134
-rw-r--r--src/client/views/collections/CollectionMenu.scss4
-rw-r--r--src/client/views/collections/CollectionMenu.tsx881
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss2
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx40
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx12
-rw-r--r--src/client/views/collections/CollectionPileView.tsx25
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx217
-rw-r--r--src/client/views/collections/CollectionStackingView.scss4
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx267
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx155
-rw-r--r--src/client/views/collections/CollectionSubView.tsx123
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx63
-rw-r--r--src/client/views/collections/CollectionTreeView.scss2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx207
-rw-r--r--src/client/views/collections/CollectionView.scss10
-rw-r--r--src/client/views/collections/CollectionView.tsx99
-rw-r--r--src/client/views/collections/TabDocView.scss2
-rw-r--r--src/client/views/collections/TabDocView.tsx122
-rw-r--r--src/client/views/collections/TreeView.scss2
-rw-r--r--src/client/views/collections/TreeView.tsx567
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx113
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx230
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx76
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx669
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx163
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx17
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss8
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx55
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss4
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx122
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss7
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx116
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss2
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx129
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx42
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx170
-rw-r--r--src/client/views/collections/goldenLayoutTheme.css132
-rw-r--r--src/client/views/global/globalCssVariables.module.scss (renamed from src/client/views/global/globalCssVariables.scss)0
-rw-r--r--src/client/views/global/globalCssVariables.module.scss.d.ts (renamed from src/client/views/global/globalCssVariables.scss.d.ts)0
-rw-r--r--src/client/views/global/globalScripts.ts82
-rw-r--r--src/client/views/linking/LinkMenu.scss6
-rw-r--r--src/client/views/linking/LinkMenu.tsx21
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx6
-rw-r--r--src/client/views/linking/LinkMenuItem.scss2
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx108
-rw-r--r--src/client/views/linking/LinkPopup.tsx5
-rw-r--r--src/client/views/linking/LinkRelationshipSearch.tsx2
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx4
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.tsx8
-rw-r--r--src/client/views/nodes/AudioBox.scss10
-rw-r--r--src/client/views/nodes/AudioBox.tsx95
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx150
-rw-r--r--src/client/views/nodes/ColorBox.scss22
-rw-r--r--src/client/views/nodes/ColorBox.tsx89
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx52
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx81
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss2
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx107
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx70
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx95
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx86
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx113
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx38
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx107
-rw-r--r--src/client/views/nodes/DocumentView.scss2
-rw-r--r--src/client/views/nodes/DocumentView.tsx628
-rw-r--r--src/client/views/nodes/EquationBox.scss2
-rw-r--r--src/client/views/nodes/EquationBox.tsx34
-rw-r--r--src/client/views/nodes/FaceRectangle.tsx19
-rw-r--r--src/client/views/nodes/FaceRectangles.tsx22
-rw-r--r--src/client/views/nodes/FieldView.tsx3
-rw-r--r--src/client/views/nodes/FontIconBox/ButtonInterface.ts10
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx137
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx44
-rw-r--r--src/client/views/nodes/ImageBox.tsx238
-rw-r--r--src/client/views/nodes/KeyValueBox.scss54
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx42
-rw-r--r--src/client/views/nodes/KeyValuePair.scss3
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx48
-rw-r--r--src/client/views/nodes/LabelBox.tsx64
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx60
-rw-r--r--src/client/views/nodes/LinkBox.tsx67
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss8
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx102
-rw-r--r--src/client/views/nodes/LoadingBox.tsx31
-rw-r--r--src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx35
-rw-r--r--src/client/views/nodes/MapBox/AnimationUtility.ts450
-rw-r--r--src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx122
-rw-r--r--src/client/views/nodes/MapBox/GeocoderControl.tsx107
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.scss79
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx495
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss134
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx1369
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx1194
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx185
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx18
-rw-r--r--src/client/views/nodes/MapBox/MapboxApiUtility.ts139
-rw-r--r--src/client/views/nodes/MapBox/MarkerIcons.tsx76
-rw-r--r--src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.pngbin0 -> 1623 bytes
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx838
-rw-r--r--src/client/views/nodes/PDFBox.scss16
-rw-r--r--src/client/views/nodes/PDFBox.tsx121
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx9
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx2
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx49
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx5
-rw-r--r--src/client/views/nodes/RadialMenu.scss14
-rw-r--r--src/client/views/nodes/RadialMenu.tsx86
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx71
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx15
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx43
-rw-r--r--src/client/views/nodes/ScriptingBox.scss15
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx67
-rw-r--r--src/client/views/nodes/TaskCompletedBox.tsx35
-rw-r--r--src/client/views/nodes/VideoBox.scss8
-rw-r--r--src/client/views/nodes/VideoBox.tsx152
-rw-r--r--src/client/views/nodes/WebBox.scss2
-rw-r--r--src/client/views/nodes/WebBox.tsx195
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.scss (renamed from src/client/views/AudioWaveform.scss)0
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.tsx122
-rw-r--r--src/client/views/nodes/audio/WaveCanvas.tsx100
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx134
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx5
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss2
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx59
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.scss468
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx87
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx9
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx390
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx6
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts4
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss11
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx100
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts2
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/TooltipTextMenu.scss102
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts2
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx8
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx2
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx7
-rw-r--r--src/client/views/nodes/trails/PresBox.scss2
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx171
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx383
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx37
-rw-r--r--src/client/views/pdf/Annotation.tsx2
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx33
-rw-r--r--src/client/views/pdf/PDFViewer.tsx180
-rw-r--r--src/client/views/search/CheckBox.scss59
-rw-r--r--src/client/views/search/CheckBox.tsx130
-rw-r--r--src/client/views/search/CollectionFilters.scss20
-rw-r--r--src/client/views/search/CollectionFilters.tsx83
-rw-r--r--src/client/views/search/IconBar.scss10
-rw-r--r--src/client/views/search/IconBar.tsx73
-rw-r--r--src/client/views/search/IconButton.scss53
-rw-r--r--src/client/views/search/IconButton.tsx126
-rw-r--r--src/client/views/search/NaviconButton.scss69
-rw-r--r--src/client/views/search/NaviconButton.tsx37
-rw-r--r--src/client/views/search/SearchBox.scss16
-rw-r--r--src/client/views/search/SearchBox.tsx7
-rw-r--r--src/client/views/search/SelectorContextMenu.scss4
-rw-r--r--src/client/views/search/ToggleBar.scss41
-rw-r--r--src/client/views/search/ToggleBar.tsx85
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.tsx2
-rw-r--r--src/client/views/topbar/TopBar.scss3
-rw-r--r--src/client/views/topbar/TopBar.tsx3
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.scss9
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx2
293 files changed, 13314 insertions, 10011 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 353e11775..6217cf04b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -7,6 +7,7 @@ import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
import { ObjectField } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
import { DocCast, StrCast } from '../fields/Types';
+import { Socket } from '../../node_modules/socket.io/dist/index';
//import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { emptyFunction, Utils } from '../Utils';
import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
@@ -56,7 +57,7 @@ export namespace DocServer {
Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
}
});
- LinkManager.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+ LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
const filtered = Array.from(references);
const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
@@ -76,7 +77,7 @@ export namespace DocServer {
json: true,
});
}
- export let _socket: SocketIOClient.Socket;
+ export 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
@@ -189,20 +190,20 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
Utils.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);
- });
+ // // 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 {
@@ -470,10 +471,13 @@ export namespace DocServer {
// to their actual RefField | undefined values. This return value either becomes the input
// argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...))
// or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])).
- return ids.reduce((map, id) => {
- map[id] = _cache[id] as any;
- return map;
- }, {} as { [id: string]: Opt<RefField> });
+ return ids.reduce(
+ (map, id) => {
+ map[id] = _cache[id] as any;
+ return map;
+ },
+ {} as { [id: string]: Opt<RefField> }
+ );
};
let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt<RefField> }> = errorFunc;
diff --git a/src/client/Network.ts b/src/client/Network.ts
index 89b31fdca..cdcd5225a 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -1,5 +1,5 @@
import { Utils } from '../Utils';
-import requestPromise = require('request-promise');
+import * as requestPromise from 'request-promise';
import { Upload } from '../server/SharedMediaTypes';
/**
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 551dca073..c8f381cc0 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -84,7 +84,7 @@ export namespace GoogleApiClientUtils {
};
try {
const schema: docs_v1.Schema$Document = await Networking.PostToServer(path, parameters);
- return schema.documentId;
+ return schema.documentId === null ? undefined : schema.documentId;
} catch {
return undefined;
}
@@ -195,7 +195,7 @@ export namespace GoogleApiClientUtils {
const title = document.title;
let bodyLines = Utils.extractText(document).text.split("\n");
options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length));
- return { title, bodyLines };
+ return { title: title ?? "", bodyLines };
}
});
};
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 6bde7989b..85634bc8b 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,4 +1,4 @@
-import { Configuration, OpenAIApi } from 'openai';
+import { ClientOptions, OpenAI } from 'openai';
enum GPTCallType {
SUMMARY = 'summary',
@@ -29,17 +29,17 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
if (callType === GPTCallType.SUMMARY) inputText += '.';
const opts: GPTCallOpts = callTypeMap[callType];
try {
- const configuration = new Configuration({
+ const configuration:ClientOptions ={
apiKey: process.env.OPENAI_KEY,
- });
- const openai = new OpenAIApi(configuration);
- const response = await openai.createCompletion({
+ };
+ const openai = new OpenAI(configuration);
+ const response = await openai.completions.create({
model: opts.model,
max_tokens: opts.maxTokens,
temperature: opts.temp,
prompt: `${opts.prompt}${inputText}`,
});
- return response.data.choices[0].text;
+ return response.choices[0].text;
} catch (err) {
console.log(err);
return 'Error connecting with API.';
@@ -48,16 +48,16 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
const gptImageCall = async (prompt: string, n?: number) => {
try {
- const configuration = new Configuration({
+ const configuration:ClientOptions = {
apiKey: process.env.OPENAI_KEY,
- });
- const openai = new OpenAIApi(configuration);
- const response = await openai.createImage({
+ };
+ const openai = new OpenAI(configuration);
+ const response = await openai.images.generate({
prompt: prompt,
n: n ?? 1,
size: '1024x1024',
});
- return response.data.data.map(data => data.url);
+ return response.data.map((data:any) => data.url);
// return response.data.data[0].url;
} catch (err) {
console.error(err);
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index 2da9927c0..ceb80acbc 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -10,7 +10,7 @@ import { DocumentView } from '../../views/nodes/DocumentView';
import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
import '../../views/nodes/WebBox.scss';
import './YoutubeBox.scss';
-import React = require('react');
+import * as React from 'react';
interface VideoTemplate {
thumbnailUrl: string;
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 87010f770..1123bcac9 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -32,11 +32,12 @@ export enum DocumentType {
IMPORT = 'import',
PRES = 'presentation',
PRESELEMENT = 'preselement',
- COLOR = 'color',
YOUTUBE = 'youtube',
COMPARISON = 'comparison',
GROUP = 'group',
PUSHPIN = 'pushpin',
+ MAPROUTE = 'maproute',
+ CALENDAR = 'calendar',
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
@@ -61,4 +62,5 @@ export enum CollectionViewType {
Pile = 'pileup',
StackedTimeline = 'stacked timeline',
NoteTaking = 'notetaking',
+ Calendar = 'calendar'
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 029653204..ff14eb101 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -20,7 +20,6 @@ import { YoutubeBox } from '../apis/youtube/YoutubeBox';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
import { DragManager, dropActionType } from '../util/DragManager';
-import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox';
import { FollowLinkScript } from '../util/LinkFollower';
import { LinkManager } from '../util/LinkManager';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
@@ -30,10 +29,8 @@ import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMu
import { CollectionView } from '../views/collections/CollectionView';
import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
-import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
import { AudioBox, media_state } from '../views/nodes/AudioBox';
-import { ColorBox } from '../views/nodes/ColorBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
import { EquationBox } from '../views/nodes/EquationBox';
@@ -61,6 +58,10 @@ import { VideoBox } from '../views/nodes/VideoBox';
import { WebBox } from '../views/nodes/WebBox';
import { SearchBox } from '../views/search/SearchBox';
import { CollectionViewType, DocumentType } from './DocumentTypes';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
+const {
+ default: { DFLT_IMAGE_NATIVE_DIM },
+} = require('../views/global/globalCssVariables.module.scss');
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
class EmptyBox {
@@ -168,15 +169,28 @@ type DTYPEt = DTypeInfo | string;
export class DocumentOptions {
// coordinate and dimensions depending on view
x?: NUMt = new NumInfo('x coordinate of document in a freeform view', false);
- y?: NUMt = new NumInfo('y coordinage of document in a freeform view', false);
+ y?: NUMt = new NumInfo('y coordinate of document in a freeform view', false);
z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]);
+ overlayX?: NUMt = new NumInfo('x coordinate of document in a overlay view', false);
+ overlayY?: NUMt = new NumInfo('y coordinate of document in a overlay view', false);
_dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
_dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
longitude?: NUMt = new NumInfo('longitude coordinate for map views', false);
+ routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordinates
+ markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document');
+ markerColor?: STRt= new StrInfo('Defines the marker color for a pushpin document');
map?: STRt = new StrInfo('text location of map');
map_type?: STRt = new StrInfo('type of map view', false);
map_zoom?: NUMt = new NumInfo('zoom of a map view', false);
+ map_pitch?: NUMt = new NumInfo('pitch of a map view', false);
+ map_bearing?: NUMt = new NumInfo('bearing of a map view', false);
+ map_style?: STRt = new StrInfo('mapbox style for a map view', false);
+
+ date_range?: STRt = new StrInfo('date range for calendar', false);
+
+ wikiData?: STRt = new StrInfo('WikiData ID related to map location');
+ description?: STRt = new StrInfo('A description of the document');
_timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
_timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden', false);
_width?: NUMt = new NumInfo('displayed width of a document');
@@ -198,6 +212,7 @@ export class DocumentOptions {
author_date?: DATEt = new DateInfo('date the document was created', true);
annotationOn?: DOCt = new DocInfo('document annotated by this document', false);
embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false);
+ rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)');
color?: STRt = new StrInfo('foreground color data doc', false);
hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
backgroundColor?: STRt = new StrInfo('background color for data doc', false);
@@ -272,6 +287,7 @@ export class DocumentOptions {
icon_label?: STRt = new StrInfo('label to use for a fontIcon doc (otherwise, the title is used)', false);
mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.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.');
dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
openFactoryLocation?: string; // an OpenWhere value to place the factory created document
@@ -292,6 +308,7 @@ export class DocumentOptions {
childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view)
childLayoutString?: string; // template string for collection to use to render its children
childDocumentsActive?: BOOLt = new BoolInfo('whether child documents are active when parent is document active');
+ childLayoutFitWidth?: BOOLt = new BoolInfo("whether a child doc's fitWith should be overriden by collection");
childDontRegisterViews?: BOOLt = new BoolInfo('whether child document views should be registered so that they can be found when following links, etc');
childHideLinkButton?: BOOLt = new BoolInfo('hide link buttons on all children');
childContextMenuFilters?: List<ScriptField>;
@@ -490,13 +507,6 @@ export namespace Docs {
},
],
[
- DocumentType.COLOR,
- {
- layout: { view: ColorBox, dataField: defaultDataKey },
- options: { _nativeWidth: 220, _nativeHeight: 300 },
- },
- ],
- [
DocumentType.IMG,
{
layout: { view: ImageBox, dataField: defaultDataKey },
@@ -570,13 +580,6 @@ export namespace Docs {
},
],
[
- DocumentType.IMPORT,
- {
- layout: { view: DirectoryImportBox, dataField: defaultDataKey },
- options: { _height: 150 },
- },
- ],
- [
DocumentType.LINK,
{
layout: { view: LinkBox, dataField: 'link' },
@@ -775,6 +778,20 @@ export namespace Docs {
options: {},
},
],
+ [
+ DocumentType.MAPROUTE,
+ {
+ layout: { view: CollectionView, dataField: defaultDataKey },
+ options: {},
+ },
+ ],
+ [
+ DocumentType.CALENDAR,
+ {
+ layout: { view: CalendarBox, dataField: defaultDataKey },
+ options: {},
+ }
+ ]
]);
const suffix = 'Proto';
@@ -1012,12 +1029,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List<Doc>([]), options);
}
- export function ColorDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options);
- }
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, '');
}
+
export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
@@ -1124,17 +1139,32 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options);
}
- export function PushpinDocument(latitude: number, longitude: number, infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
+ export function PushpinDocument(
+ latitude: number,
+ longitude: number,
+ infoWindowOpen: boolean,
+ documents: Array<Doc>,
+ options: DocumentOptions,
+ id?: string) {
+
return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude, longitude, infoWindowOpen, ...options }, id);
}
+ export function MapRouteDocument(infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), { infoWindowOpen, ...options }, id);
+ }
+
+ export function CalendarDocument(options: DocumentOptions, documents: Array<Doc>){
+ return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), {...options})
+ }
+
// shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
// export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
// return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
// }
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id);
+ const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id);
documents.forEach(d => Doc.SetContainer(d, inst));
return inst;
}
@@ -1182,6 +1212,10 @@ export namespace Docs {
return doc;
}
+ export function CalendarCollectionDocument(documents: Array<Doc>, options: DocumentOptions){
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), {...options, _type_collection: CollectionViewType.Calendar});
+ }
+
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId);
}
@@ -1217,7 +1251,7 @@ export namespace Docs {
}
export function FunctionPlotDocument(documents: Array<Doc>, options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.FUNCPLOT), new List(documents), { ...(options || {}) });
+ return InstanceFromProto(Prototypes.get(DocumentType.FUNCPLOT), new List(documents), { title: 'func plot', ...(options || {}) });
}
export function ButtonDocument(options?: DocumentOptions) {
@@ -1242,10 +1276,6 @@ export namespace Docs {
return ret;
}
- export function DirectoryImportDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List<Doc>(), options);
- }
-
export type DocConfig = {
doc: Doc;
initialWidth?: number;
@@ -1294,7 +1324,7 @@ export namespace DocUtils {
// 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('=');
+ const linkedToExp = (value ?? '').split('=');
if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
};
@@ -1464,7 +1494,7 @@ export namespace DocUtils {
{
'acl-Guest': SharingPermissions.Augment,
'_acl-Guest': SharingPermissions.Augment,
- title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
+ title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
link_displayLine: linkSettings.link_displayLine,
@@ -1487,7 +1517,7 @@ export namespace DocUtils {
Object.keys(scripts).map(key => {
const script = scripts[key];
if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
- doc[key] = ScriptField.MakeScript(script, {
+ (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
self: Doc.name,
this: Doc.name,
dragData: DragManager.DocumentDragData.name,
@@ -1511,7 +1541,7 @@ export namespace DocUtils {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
const setFunc = Cast(funcs[key + '-setter'], 'string', null);
- doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
+ (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
}
});
return doc;
@@ -1632,7 +1662,7 @@ export namespace DocUtils {
options = { ...options, _width: 400, _height: 512, title: path };
}
- return ctor ? ctor(path, options, overwriteDoc) : undefined;
+ return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined;
}
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
@@ -1673,7 +1703,7 @@ export namespace DocUtils {
newDoc.x = x;
newDoc.y = y;
EquationBox.SelectOnLoad = newDoc[Id];
- if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
+ if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
if (pivotField) {
newDoc[pivotField] = pivotValue;
}
@@ -1717,7 +1747,7 @@ export namespace DocUtils {
}
export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
const templateName = templateSignature.replace(/\(.*\)/, '');
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
const customName = 'layout_' + templateSignature;
const _width = NumCast(doc._width);
@@ -1904,7 +1934,7 @@ export namespace DocUtils {
const generatedDocuments: Doc[] = [];
Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
const {
- source: { name, type },
+ source: { newFilename, originalFilename, mimetype },
result,
} = upfiles.lastElement();
if ((result as any).message) {
@@ -1913,7 +1943,7 @@ export namespace DocUtils {
overwriteDoc.loadingError = (result as any).message;
LoadingBox.removeCurrentlyLoading(overwriteDoc);
}
- } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
+ } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
});
}
@@ -1933,10 +1963,10 @@ export namespace DocUtils {
const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
for (const {
- source: { name, type },
+ source: { newFilename, mimetype },
result,
} of upfiles) {
- name && type && processFileupload(generatedDocuments, name, type, result, options);
+ newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
}
return generatedDocuments;
}
@@ -1946,15 +1976,15 @@ export namespace DocUtils {
// Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid.
Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
const {
- source: { name, type },
+ source: { newFilename, mimetype },
result,
- } = upfiles.lastElement() ?? { source: { name: '', type: '' }, result: { message: 'upload failed' } };
+ } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } };
if ((result as any).message) {
if (overwriteDoc) {
overwriteDoc.loadingError = (result as any).message;
LoadingBox.removeCurrentlyLoading(overwriteDoc);
}
- } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
+ } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
});
}
@@ -1987,9 +2017,9 @@ ScriptingGlobals.add(function makeDelegate(proto: any) {
const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
return d;
});
-ScriptingGlobals.add(function generateLinkTitle(self: Doc) {
- const link_anchor_1title = self.link_anchor_1 && self.link_anchor_1 !== self ? Cast(self.link_anchor_1, Doc, null)?.title : '<?>';
- const link_anchor_2title = self.link_anchor_2 && self.link_anchor_2 !== self ? Cast(self.link_anchor_2, Doc, null)?.title : '<?>';
- const relation = self.link_relationship || 'to';
+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 relation = link.link_relationship || 'to';
return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`;
-});
+}); \ No newline at end of file
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index a224b84f4..11f16493f 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
@@ -15,13 +15,14 @@ export class BranchingTrailManager extends React.Component {
constructor(props: any) {
super(props);
+ makeObservable(this);
if (!BranchingTrailManager.Instance) {
BranchingTrailManager.Instance = this;
}
}
setupUi = () => {
- OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail'});
+ OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
// OverlayView.Instance.forceUpdate();
console.log(OverlayView.Instance);
// let hi = Docs.Create.TextDocument("beee", {
@@ -30,11 +31,10 @@ export class BranchingTrailManager extends React.Component {
// })
// hi.overlayX = 100;
// hi.overlayY = 100;
-
+
// Doc.AddToMyOverlay(hi);
console.log(DocumentManager._overlayViews);
};
-
// stack of the history
@observable private slideHistoryStack: String[] = [];
@@ -69,7 +69,7 @@ export class BranchingTrailManager extends React.Component {
if (this.prevPresId === null || this.prevPresId !== presId) {
Doc.UserDoc().isBranchingMode = true;
this.setPrevPres(presId);
-
+
// REVERT THE SET
const stringified = [presId, targetDocId].toString();
if (this.containsSet.has([presId, targetDocId].toString())) {
@@ -98,7 +98,7 @@ export class BranchingTrailManager extends React.Component {
const newStack = this.slideHistoryStack.slice(0, removeIndex);
const removed = this.slideHistoryStack.slice(removeIndex);
-
+
this.setSlideHistoryStack(newStack);
removed.forEach(info => this.containsSet.delete(info.toString()));
diff --git a/src/client/util/CalendarManager.scss b/src/client/util/CalendarManager.scss
new file mode 100644
index 000000000..114e19a0e
--- /dev/null
+++ b/src/client/util/CalendarManager.scss
@@ -0,0 +1,62 @@
+.calendar-interface{
+ width: 600px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 7px;
+ padding: 10px;
+
+ .selected-doc-title{
+ font-size: 1.4em;
+ }
+
+ .creation-type-container{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ align-self: center;
+ gap: 12px;
+
+ .calendar-creation{
+ cursor: pointer;
+ }
+
+ .calendar-creation-selected{
+ border-bottom: 2px solid white;
+ }
+ }
+
+ .choose-calendar-container{
+ margin-top: 10px;
+ align-self: center;
+ width: 60%;
+ }
+
+ .description-container{
+ margin-top: 10px;
+ align-self: center;
+ width: 60%;
+ }
+
+ .date-range-picker-container{
+ margin-top: 5px;
+ align-self: center;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+
+ .create-button-container{
+
+ margin-top: 5px;
+ align-self: center;
+
+ .button-content{
+ font-size: 1.2em;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #EFF2F7;
+ }
+ }
+}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
new file mode 100644
index 000000000..6ef2d3429
--- /dev/null
+++ b/src/client/util/CalendarManager.tsx
@@ -0,0 +1,358 @@
+import * as React from 'react';
+import './CalendarManager.scss';
+import { observer } from 'mobx-react';
+import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { MainViewModal } from '../views/MainViewModal';
+import { TextField } from '@mui/material';
+import Select from 'react-select';
+import { SettingsManager } from './SettingsManager';
+import { DateCast, DocCast, StrCast } from '../../fields/Types';
+import { SelectionManager } from './SelectionManager';
+import { DocumentManager } from './DocumentManager';
+import { DocData } from '../../fields/DocSymbols';
+// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
+import { Button } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Provider, defaultTheme } from '@adobe/react-spectrum';
+import { DateValue } from '@internationalized/date';
+import { DateRangePicker, SpectrumDateRangePickerProps } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+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';
+
+type CreationType = 'new-calendar' | 'existing-calendar' | 'manage-calendars';
+
+interface CalendarSelectOptions {
+ label: string;
+ value: string;
+}
+
+const formatCalendarDateToString = (calendarDate: any) => {
+ console.log('Formatting the following date: ', calendarDate);
+ const date = new Date(calendarDate.year, calendarDate.month - 1, calendarDate.day);
+ console.log(typeof date);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+
+ return `${year}-${month}-${day}`;
+};
+
+// TODO: If doc is already part of a calendar, display that
+// TODO: For a doc already in a calendar: give option to edit date range, delete from calendar
+
+@observer
+export class CalendarManager extends ObservableReactComponent<{}> {
+ public static Instance: CalendarManager;
+ @observable private isOpen = false;
+ @observable private targetDoc: Doc | undefined; // the target document
+ @observable private targetDocView: DocumentView | 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
+
+ @observable private creationType: CreationType = 'new-calendar';
+
+ @observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars?.data);
+
+ @computed get selectOptions() {
+ return this.existingCalendars.map(calendar => ({ label: StrCast(calendar.title), value: StrCast(calendar.title) }));
+ }
+
+ @observable
+ selectedExistingCalendarOption: CalendarSelectOptions | null = null;
+
+ @observable
+ calendarName: string = '';
+
+ @observable
+ calendarDescription: string = '';
+
+ @observable
+ errorMessage: string = '';
+
+ @action
+ setInterationType = (type: CreationType) => {
+ this.errorMessage = '';
+ this.calendarName = '';
+ this.creationType = type;
+ };
+
+ public open = (target?: DocumentView, target_doc?: Doc) => {
+ console.log('hi');
+ runInAction(() => {
+ this.targetDoc = target_doc || target?.Document;
+ this.targetDocView = target;
+ DictationOverlay.Instance.hasActiveModal = true;
+ this.isOpen = this.targetDoc !== undefined;
+ });
+ };
+
+ public close = action(() => {
+ this.isOpen = false;
+ TaskCompletionBox.taskCompleted = false;
+ setTimeout(
+ action(() => {
+ DictationOverlay.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
+ this.layoutDocAcls = false;
+ });
+
+ constructor(props: {}) {
+ super(props);
+ CalendarManager.Instance = this;
+ makeObservable(this);
+ }
+
+ componentDidMount(): void {}
+
+ @action
+ handleSelectChange = (option: any) => {
+ if (option) {
+ let selectOpt = option as CalendarSelectOptions;
+ this.selectedExistingCalendarOption = selectOpt;
+ this.calendarName = selectOpt.value; // or label
+ }
+ };
+
+ @action
+ handleCalendarTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ console.log('Existing calendars: ', this.existingCalendars);
+ this.calendarName = event.target.value;
+ };
+
+ @action
+ handleCalendarDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ this.calendarDescription = event.target.value;
+ };
+
+ // TODO: Make undoable
+ private addToCalendar = () => {
+ let 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);
+ if (targetDoc) {
+ let calendar: Doc;
+ if (this.creationType === 'new-calendar') {
+ if (!this.existingCalendars.find(doc => StrCast(doc.title) === this.calendarName)) {
+ console.log('creating...');
+ calendar = Docs.Create.CalendarDocument(
+ {
+ title: this.calendarName,
+ description: this.calendarDescription,
+ },
+ []
+ );
+ console.log('successful calendar creation');
+ } else {
+ this.errorMessage = 'Calendar with this name already exists';
+ return;
+ }
+ } else {
+ // find existing calendar based on selected name (should technically always find one)
+ const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ if (existingCalendar) calendar = existingCalendar;
+ else {
+ this.errorMessage = 'Must select an existing calendar';
+ return;
+ }
+ }
+ // Get start and end date strings
+ const startDateStr = formatCalendarDateToString(this.selectedDateRange.start);
+ const endDateStr = formatCalendarDateToString(this.selectedDateRange.end);
+
+ console.log('start date: ', startDateStr);
+ console.log('end date: ', endDateStr);
+
+ const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
+ console.log('subdoc embedding', subDocEmbedding);
+ subDocEmbedding.embedContainer = calendar; // set embed container
+ subDocEmbedding.date_range = `${startDateStr}|${endDateStr}`; // set subDoc date range
+
+ Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
+
+ console.log('my calendars: ', Doc.MyCalendars);
+ if (this.creationType === 'new-calendar') {
+ Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
+ }
+ }
+ };
+
+ private focusOn = (contents: string) => {
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+ const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.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>
+ );
+ };
+
+ @observable
+ selectedDateRange: any = [
+ {
+ start: new Date(),
+ end: new Date(),
+ },
+ ];
+
+ @action
+ setSelectedDateRange = (range: any) => {
+ console.log('Range: ', range);
+ this.selectedDateRange = range;
+ };
+
+ @computed
+ get createButtonActive() {
+ if (this.calendarName.length === 0 || this.errorMessage.length > 0) return false; // disabled if no calendar name
+ let startDate: Date | undefined;
+ let endDate: Date | undefined;
+ try {
+ startDate = this.selectedDateRange.start;
+ endDate = this.selectedDateRange.end;
+ console.log(startDate);
+ console.log(endDate);
+ } catch (e: any) {
+ console.log(e);
+ return false; // disabled
+ }
+ if (!startDate || !endDate) return false; // disabled if any is undefined
+ return true;
+ }
+
+ @computed
+ get calendarInterface() {
+ let 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"
+ style={{
+ background: SettingsManager.userBackgroundColor,
+ color: StrCast(Doc.UserDoc().userColor),
+ }}>
+ <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+ <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')}>
+ Add to New Calendar
+ </div>
+ <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+ Add to Existing calendar
+ </div>
+ </div>
+ <div className="choose-calendar-container">
+ {this.creationType === 'new-calendar' ? (
+ <TextField
+ fullWidth
+ onChange={this.handleCalendarTitleChange}
+ label="Calendar name"
+ placeholder="Enter a name..."
+ variant="filled"
+ style={{
+ backgroundColor: 'white',
+ color: 'black',
+ borderRadius: '5px',
+ }}
+ />
+ ) : (
+ <Select
+ className="existing-calendar-search"
+ placeholder="Search for existing calendar..."
+ isClearable
+ isSearchable
+ options={this.selectOptions}
+ value={this.selectedExistingCalendarOption}
+ onChange={this.handleSelectChange}
+ styles={{
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
+ indicatorSeparator: () => ({
+ display: 'inline-flex',
+ visibility: 'hidden',
+ }),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
+ }}></Select>
+ )}
+ </div>
+ <div className="description-container">
+ <TextField
+ fullWidth
+ multiline
+ label="Calendar description"
+ placeholder="Enter a description (optional)..."
+ onChange={this.handleCalendarDescriptionChange}
+ variant="filled"
+ style={{
+ backgroundColor: 'white',
+ color: 'black',
+ borderRadius: '5px',
+ }}
+ />
+ </div>
+ <div className="date-range-picker-container">
+ <div>Select a date range: </div>
+ <Provider theme={defaultTheme}>
+ <DateRangePicker aria-label="Select a date range" value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} />
+ </Provider>
+ </div>
+ {this.createButtonActive && (
+ <div className="create-button-container">
+ <Button onClick={() => this.addToCalendar()} text="Add to Calendar" iconPlacement="right" icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ render() {
+ return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ }
+}
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index 11e31fe2e..8679a0101 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -1,4 +1,4 @@
-@import "../views/global/globalCssVariables";
+@import '../views/global/globalCssVariables.module';
.capture-interface {
//background-color: whitesmoke !important;
@@ -39,7 +39,7 @@
align-items: center;
justify-content: center;
}
-
+
.recordButtonInner {
border-radius: 100%;
width: 70%;
@@ -106,7 +106,7 @@
display: flex;
justify-content: center;
align-items: center;
- background-color: #BDDBE8;
+ background-color: #bddbe8;
border-radius: 100%;
font-weight: 800;
margin-right: 5px;
@@ -154,4 +154,3 @@
}
}
}
-
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 8a4f37121..071fc1ee9 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
@@ -20,6 +20,7 @@ export class CaptureManager extends React.Component<{}> {
constructor(props: {}) {
super(props);
+ makeObservable(this);
CaptureManager.Instance = this;
}
@@ -81,7 +82,7 @@ export class CaptureManager extends React.Component<{}> {
<div
className="cancel"
onClick={() => {
- const selected = SelectionManager.Views().slice();
+ const selected = SelectionManager.Views.slice();
SelectionManager.DeselectAll();
selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
this.close();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1068e26bb..f7070a862 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -67,7 +67,7 @@ export class CurrentUserUtils {
templateOpts: { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, isSystem: true },
template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
[
- Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, isSystem: true }),
+ Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
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)
},
@@ -97,7 +97,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
- _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, isSystem: true, _forceActive: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -110,8 +110,8 @@ export class CurrentUserUtils {
const tempClicks = DocCast(doc[field]);
const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
- { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
- { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
+ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
+ { 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;
@@ -274,17 +274,17 @@ export class CurrentUserUtils {
{key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
{key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }},
{key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
- {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
+ {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
{key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
- {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
+ {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree,
treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
dropAction:'move', treeView_Type: TreeViewType.outline,
backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true
- }, funcs: {title: 'self.text?.Text'}},
+ }, funcs: {title: 'this.text?.Text'}},
];
emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
@@ -339,7 +339,7 @@ export class CurrentUserUtils {
/// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents
static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] {
- const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target?.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())";
+ const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(this.target?.data).filter(doc => !docList(this.target.viewed).includes(doc)).length.toString())";
const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails";
return [
{ title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
@@ -351,7 +351,7 @@ export class CurrentUserUtils {
{ title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}},
{ title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}},
{ title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
- ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}}));
+ ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(this)'}}));
}
/// the empty panel that is filled with whichever left menu button's panel has been selected
@@ -467,6 +467,7 @@ export class CurrentUserUtils {
const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
+ layout_explainer: "This is a palette of documents that can be created.",
_layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
@@ -479,14 +480,14 @@ export class CurrentUserUtils {
const newDashboard = `createNewDashboard()`;
const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
- title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
+ title: "new Dash", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
const contextMenuScripts = [/*newDashboard*/] as string[];
const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
const contextMenuIcons = [/*"plus"*/] as string[];
- const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
+ 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
const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
@@ -558,9 +559,9 @@ export class CurrentUserUtils {
const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, color: Colors.BLACK, buttonText: "Empty", icon: "trash", isSystem: true,
toolTip: "Empty recently closed",};
- DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
+ DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")});
- if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
+ if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) {
recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
}
return recentlyClosed;
@@ -589,7 +590,7 @@ export class CurrentUserUtils {
})
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
- btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true,
+ btnType: ButtonType.ToolButton, _layout_hideContextMenu: true,
_dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]),
/*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
})
@@ -628,42 +629,42 @@ export class CurrentUserUtils {
}
static stackTools(): Button[] {
return [
- { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static viewTools(): Button[] {
return [
- { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "View All", icon: "object-group", toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "View All", icon: "object-group", toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
// want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
- { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
return [
- { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
+ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
- { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
- { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
- { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
- { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Vcenter", toolTip: "Vertical center", btnType: ButtonType.ToggleButton, icon: "pallet", toolType:"vcent", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
+ { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
+ { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Vcenter", toolTip: "Vertical center", btnType: ButtonType.ToggleButton, icon: "pallet", toolType:"vcent", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true,
subMenu: [
- { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
- { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}' }},
+ { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
]},
- { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
- { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
+ { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
+ { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
// { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
@@ -672,15 +673,15 @@ export class CurrentUserUtils {
static inkTools():Button[] {
return [
- { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
- { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.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(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.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(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.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(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
- { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
- { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnMin: 1},
- { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
+ { 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: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
+ { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
+ { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} },
];
}
@@ -688,7 +689,6 @@ export class CurrentUserUtils {
return [
{title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
{title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
- {title: "DataViz",toolTip: "Turn Schema Table into Data Visualization Doc", btnType: ButtonType.ClickButton, icon: "chart-bar", scripts:{ onClick: '{ datavizFromSchema()'} },
];
}
@@ -710,17 +710,17 @@ export class CurrentUserUtils {
{ title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
{ title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
{ title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
- { title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
- { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
- { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
- { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+ { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
];
}
@@ -816,7 +816,7 @@ export class CurrentUserUtils {
// When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
// The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
- const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.rootDoc);}";
+ const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.Document);}";
const sharedScripts = { treeView_ChildDoubleClick: dblClkScript, }
const sharedDocOpts:DocumentOptions = {
@@ -905,7 +905,7 @@ export class CurrentUserUtils {
this.setupDocTemplates(doc); // sets up the template menu of templates
//this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
- DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
@@ -946,12 +946,12 @@ export class CurrentUserUtils {
runInAction(() => CurrentUserUtils.ServerVersion = result.version);
Doc.CurrentUserEmail = result.email;
resolvedPorts = result.resolvedPorts as any;
- DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
if (result.cacheDocumentIds)
{
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
- for (let i = 0; i < ids.length; i = Math.min(ids.length, i+batch)) {
+ for (let i = 0; i < ids.length; i += batch) {
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
}
@@ -977,6 +977,7 @@ export class CurrentUserUtils {
DashboardView.createNewDashboard(undefined, "guest dashboard");
} else {
userDoc.activePage = "home";
+ userDoc.noviceMode = true;
}
}
return userDoc;
@@ -1021,4 +1022,4 @@ ScriptingGlobals.add(function IsExploreMode() { return DocumentView.ExploreMode;
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
+ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 0fd7e840c..039bb360e 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -236,7 +236,7 @@ export namespace DictationManager {
export const execute = async (phrase: string) => {
return UndoManager.RunInBatch(async () => {
console.log('PHRASE: ' + phrase);
- const targets = SelectionManager.Views();
+ const targets = SelectionManager.Views;
if (!targets || !targets.length) {
return;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3c59a8060..4816f3317 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableSet, observe } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -13,17 +13,22 @@ import { LightboxView } from '../views/LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
import { KeyValueBox } from '../views/nodes/KeyValueBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
-import { LoadingBox } from '../views/nodes/LoadingBox';
import { PresBox } from '../views/nodes/trails';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
const { Howl } = require('howler');
export class DocumentManager {
+ private static _instance: DocumentManager;
+ public static get Instance(): DocumentManager {
+ return this._instance || (this._instance = new this());
+ }
+
//global holds all of the nodes (regardless of which collection they're in)
@observable _documentViews = new Set<DocumentView>();
- @observable public LinkAnchorBoxViews: DocumentView[] = [];
- @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
+ @observable.shallow public CurrentlyLoading: Doc[] = [];
+ @observable.shallow public LinkAnchorBoxViews: DocumentView[] = [];
+ @observable.shallow public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
@computed public get DocumentViews() {
return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
}
@@ -34,24 +39,19 @@ export class DocumentManager {
this._documentViews.delete(dv);
}
- private static _instance: DocumentManager;
- public static get Instance(): DocumentManager {
- return this._instance || (this._instance = new this());
- }
-
//private constructor so no other class can create a nodemanager
private constructor() {
- if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
- observe(LoadingBox.CurrentlyLoading, change => {
+ makeObservable(this);
+ observe(this.CurrentlyLoading, change => {
// watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
switch (change.type as any) {
case 'update':
break;
case 'remove':
- // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()));
+ // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()));
break;
case 'splice':
- (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
+ (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;
}
});
@@ -72,7 +72,7 @@ export class DocumentManager {
return false;
};
callAddViewFuncs = (view: DocumentView) => {
- const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.rootDoc);
+ const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.Document);
if (callFuncs.length) {
this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => !callFuncs.includes(vc));
const intTimer = setInterval(
@@ -89,11 +89,11 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- if (view.props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
- if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const viewAnchorIndex = view.props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
- const link = view.rootDoc;
- this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
+ if (view._props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
+ if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ const viewAnchorIndex = view._props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
+ const link = view.Document;
+ this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.Document, link) && !dv._props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
this.LinkedDocumentViews.push({
a: viewAnchorIndex === 'link_anchor_2' ? otherView : view,
b: viewAnchorIndex === 'link_anchor_2' ? view : otherView,
@@ -116,7 +116,7 @@ export class DocumentManager {
})
);
- if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const index = this.LinkAnchorBoxViews.indexOf(view);
this.LinkAnchorBoxViews.splice(index, 1);
} else {
@@ -129,13 +129,13 @@ export class DocumentManager {
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
- if (view.rootDoc[Id] === id) {
+ if (view.Document[Id] === id) {
toReturn.push(view);
}
});
if (toReturn.length === 0) {
DocumentManager.Instance.DocumentViews.forEach(view => {
- if (Doc.GetProto(view.rootDoc)?.[Id] === id) {
+ if (Doc.GetProto(view.Document)?.[Id] === id) {
toReturn.push(view);
}
});
@@ -153,29 +153,29 @@ export class DocumentManager {
return passes.reduce(
(toReturn, pass) =>
toReturn ??
- docViewArray.filter(view => view.rootDoc === target).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection) ??
- docViewArray.filter(view => Doc.AreProtosEqual(view.rootDoc, target)).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection),
+ docViewArray.filter(view => view.Document === target).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection) ??
+ docViewArray.filter(view => Doc.AreProtosEqual(view.Document, target)).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection),
undefined as Opt<DocumentView>
);
}
public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
const views: DocumentView[] = [];
- DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.IsLightboxDocView(view.docViewPath) && 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);
};
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.rootDoc !== originatingDoc);
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ 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.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
- // ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
- // )?.rootDoc ??
+ // ((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;
const toReturn: DocumentView[] = [];
@@ -185,10 +185,10 @@ export class DocumentManager {
// heuristic to return the "best" documents first:
// choose a document in the lightbox first
// choose an exact match over an embedding match
- lightViews.map(view => view.rootDoc === toFind && toReturn.push(view));
- lightViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
- docViews.map(view => view.rootDoc === toFind && toReturn.push(view));
- docViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
+ lightViews.map(view => view.Document === toFind && toReturn.push(view));
+ lightViews.map(view => view.Document !== toFind && Doc.AreProtosEqual(view.Document, toFind) && toReturn.push(view));
+ docViews.map(view => view.Document === toFind && toReturn.push(view));
+ docViews.map(view => view.Document !== toFind && Doc.AreProtosEqual(view.Document, toFind) && toReturn.push(view));
return toReturn;
}
@@ -240,8 +240,8 @@ export class DocumentManager {
const docViewPath = targetDocView.docViewPath.slice();
let rootContextView = docViewPath.shift();
await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
- if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
- else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
+ 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);
};
// shows a document by first:
@@ -299,14 +299,14 @@ export class DocumentManager {
let contextView: DocumentView | undefined; // view containing context that contains target
let focused = false;
while (true) {
- if (docView.rootDoc.layout_fieldKey === 'layout_icon') {
+ if (docView.Document.layout_fieldKey === 'layout_icon') {
await new Promise<void>(res => docView.iconify(res));
options.didMove = true;
}
- const nextFocus = docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ 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
const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView, focused };
+ if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
contextView = options.anchorDoc?.layout_unrendered ? childDocView : docView;
docView = childDocView;
}
@@ -316,14 +316,14 @@ export class DocumentManager {
restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
if (viewSpec && docView) {
//if (docView.ComponentView instanceof FormattedTextBox)
- //viewSpec !== docView.rootDoc &&
+ //viewSpec !== docView.Document &&
docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
- Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect);
- if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode));
- if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc);
- if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden;
- if (options.effect) docView.rootDoc[Animation] = options.effect;
+ Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
+ if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode));
+ if (options.playAudio) DocumentManager.playAudioAnno(docView.Document);
+ 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 && viewSpec.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
@@ -333,7 +333,7 @@ export class DocumentManager {
DocumentManager._overlayViews.add(contextView);
}
Doc.AddUnHighlightWatcher(() => {
- docView.rootDoc[Animation] = undefined;
+ docView.Document[Animation] = undefined;
DocumentManager.removeOverlayViews();
contextView && (contextView.htmlOverlayEffect = undefined);
});
@@ -344,8 +344,8 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
const func = () => {
const cv = DocumentManager.Instance.getDocumentView(containingDoc);
const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
- DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
+ if (dv && (!containingDoc || dv._props.docViewPath().lastElement()?.Document === containingDoc)) {
+ DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
} else {
const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
@@ -353,7 +353,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
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.rootDoc);
+ dv && Doc.linkFollowHighlight(dv.Document);
});
}
};
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 6e4de252d..c711db31a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -7,13 +7,13 @@ import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast, StrCast } from '../../fields/Types';
import { emptyFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
-import * as globalCssVariables from '../views/global/globalCssVariables.scss';
import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../views/nodes/DocumentView';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
+const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
@@ -85,7 +85,16 @@ export namespace DragManager {
// event called when the drag operation results in a drop action
export class DropEvent {
- constructor(readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean) {}
+ constructor(
+ readonly x: number,
+ readonly y: number,
+ readonly complete: DragCompleteEvent,
+ readonly shiftKey: boolean,
+ readonly altKey: boolean,
+ readonly metaKey: boolean,
+ readonly ctrlKey: boolean,
+ readonly embedKey: boolean
+ ) {}
}
// event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -209,16 +218,14 @@ export namespace DragManager {
!dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
: docDragData.dropAction === 'embed'
- ? Doc.BestEmbedding(d)
- : docDragData.dropAction === 'add'
- ? d
- : docDragData.dropAction === 'proto'
- ? Doc.GetProto(d)
- : docDragData.dropAction === 'copy'
- ? (
- await Doc.MakeClone(d)
- ).clone
- : d
+ ? Doc.BestEmbedding(d)
+ : docDragData.dropAction === 'add'
+ ? d
+ : docDragData.dropAction === 'proto'
+ ? Doc.GetProto(d)
+ : docDragData.dropAction === 'copy'
+ ? (await Doc.MakeClone(d)).clone
+ : d
)
)
).filter(d => d);
@@ -261,7 +268,7 @@ export namespace DragManager {
// drags a linker button and creates a link on drop
export function StartLinkDrag(ele: HTMLElement, sourceView: DocumentView, sourceDocGetAnchor: undefined | ((addAsAnnotation: boolean) => Doc), downX: number, downY: number, options?: DragOptions) {
- StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.(true) ?? sourceView.rootDoc), downX, downY, options);
+ StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.(true) ?? sourceView.Document), downX, downY, options);
}
// drags a column from a schema view
@@ -287,14 +294,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, i) => {
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, i) => {
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;
@@ -318,11 +325,11 @@ export namespace DragManager {
return drag;
};
return {
- x: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
- y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()),
+ x: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.VertSnapLines),
+ y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines),
};
}
- export let docsBeingDragged: Doc[] = observable([] as Doc[]);
+ 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) {
@@ -428,7 +435,7 @@ export namespace DragManager {
color: 'black',
transition: 'none',
borderRadius: getComputedStyle(ele).borderRadius,
- zIndex: globalCssVariables.contextMenuZindex,
+ zIndex: contextMenuZindex,
transformOrigin: '0 0',
width,
height,
@@ -463,7 +470,7 @@ export namespace DragManager {
runInAction(() => docsBeingDragged.push(...docsToDrag));
const hideDragShowOriginalElements = (hide: boolean) => {
- dragLabel.style.display = hide && !SnappingManager.GetCanEmbed() ? '' : 'none';
+ dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
};
@@ -564,7 +571,7 @@ export namespace DragManager {
);
scrollAwaiter && clearTimeout(scrollAwaiter);
- SnappingManager.GetIsDragging() && (scrollAwaiter = setTimeout(autoScrollHandler, 25));
+ SnappingManager.IsDragging && (scrollAwaiter = setTimeout(autoScrollHandler, 25));
};
scrollAwaiter && clearTimeout(scrollAwaiter);
scrollAwaiter = setTimeout(autoScrollHandler, 250);
@@ -597,7 +604,7 @@ export namespace DragManager {
altKey: e.altKey,
metaKey: e.metaKey,
ctrlKey: e.ctrlKey,
- embedKey: SnappingManager.GetCanEmbed(),
+ embedKey: SnappingManager.CanEmbed,
},
};
target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
@@ -611,7 +618,7 @@ export namespace DragManager {
ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
if (readOnly) {
- return SelectionManager.Views().some(dv => dv.rootDoc.keepZWhenDragged);
+ return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged);
}
- SelectionManager.Views().map(dv => (dv.rootDoc.keepZWhenDragged = !dv.rootDoc.keepZWhenDragged));
+ SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
});
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 8973306bf..90f65b648 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
@@ -17,6 +17,7 @@ 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
@@ -27,7 +28,7 @@ export interface UserOptions {
}
@observer
-export class GroupManager extends React.Component<{}> {
+export class GroupManager extends ObservableReactComponent<{}> {
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.
@@ -41,6 +42,7 @@ export class GroupManager extends React.Component<{}> {
constructor(props: Readonly<{}>) {
super(props);
+ makeObservable(this);
GroupManager.Instance = this;
}
@@ -52,7 +54,7 @@ export class GroupManager extends React.Component<{}> {
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- if (Doc.UserDoc()[Id] !== '__guest__') {
+ if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
@@ -258,10 +260,7 @@ export class GroupManager extends React.Component<{}> {
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';
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 18aee6444..2f1a336cc 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -85,7 +85,7 @@ export namespace HistoryUtil {
};
function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) {
- function parse(parser: ParserValue, value: string | string[] | null | undefined) {
+ function parse(parser: ParserValue, value: string | (string | null)[] | null | undefined) {
if (value === undefined || value === null) {
return value;
}
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 151f18d6f..990798ed3 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -27,7 +27,7 @@ export namespace Hypothesis {
* Search for a WebDocument whose url field matches the given uri, return undefined if not found
*/
export const findWebDoc = async (uri: string) => {
- const currentDoc = SelectionManager.Docs().lastElement();
+ const currentDoc = SelectionManager.Docs.lastElement();
if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
const results: Doc[] = [];
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 1a4c2450e..b6dbea33a 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,440 +1,439 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { BatchedArray } from 'array-batcher';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import { extname } from 'path';
-import Measure, { ContentRect } from 'react-measure';
-import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
-import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
-import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { BoolCast, Cast, NumCast } from '../../../fields/Types';
-import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes';
-import { Utils } from '../../../Utils';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from '../../Network';
-import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-import { DocumentManager } from '../DocumentManager';
-import './DirectoryImportBox.scss';
-import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
-import React = require('react');
-import _ = require('lodash');
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { BatchedArray } from 'array-batcher';
+// import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+// import { observer } from 'mobx-react';
+// import { extname } from 'path';
+// import Measure, { ContentRect } from 'react-measure';
+// import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
+// import { Id } from '../../../fields/FieldSymbols';
+// import { List } from '../../../fields/List';
+// import { listSpec } from '../../../fields/Schema';
+// import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+// import { BoolCast, Cast, NumCast } from '../../../fields/Types';
+// import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes';
+// import { Utils } from '../../../Utils';
+// import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
+// import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+// import { DocumentType } from '../../documents/DocumentTypes';
+// import { Networking } from '../../Network';
+// import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
+// import { DocumentManager } from '../DocumentManager';
+// import './DirectoryImportBox.scss';
+// import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
+// import * as React from 'react';
-const unsupported = ['text/html', 'text/plain'];
+// const unsupported = ['text/html', 'text/plain'];
-@observer
-export class DirectoryImportBox extends React.Component<FieldViewProps> {
- private selector = React.createRef<HTMLInputElement>();
- @observable private top = 0;
- @observable private left = 0;
- private dimensions = 50;
- @observable private phase = '';
- private disposer: Opt<IReactionDisposer>;
+// @observer
+// export class DirectoryImportBox extends React.Component<FieldViewProps> {
+// private selector = React.createRef<HTMLInputElement>();
+// @observable private top = 0;
+// @observable private left = 0;
+// private dimensions = 50;
+// @observable private phase = '';
+// private disposer: Opt<IReactionDisposer>;
- @observable private entries: ImportMetadataEntry[] = [];
+// @observable private entries: ImportMetadataEntry[] = [];
- @observable private quota = 1;
- @observable private completed = 0;
+// @observable private quota = 1;
+// @observable private completed = 0;
- @observable private uploading = false;
- @observable private removeHover = false;
+// @observable private uploading = false;
+// @observable private removeHover = false;
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(DirectoryImportBox, fieldKey);
- }
+// public static LayoutString(fieldKey: string) {
+// return FieldView.LayoutString(DirectoryImportBox, fieldKey);
+// }
- constructor(props: FieldViewProps) {
- super(props);
- const doc = this.props.Document;
- this.editingMetadata = this.editingMetadata || false;
- this.persistent = this.persistent || false;
- !Cast(doc.data, listSpec(Doc)) && (doc.data = new List<Doc>());
- }
+// constructor(props: FieldViewProps) {
+// super(props);
+// const doc = this.props.Document;
+// this.editingMetadata = this.editingMetadata || false;
+// this.persistent = this.persistent || false;
+// !Cast(doc.data, listSpec(Doc)) && (doc.data = new List<Doc>());
+// }
- @computed
- private get editingMetadata() {
- return BoolCast(this.props.Document.editingMetadata);
- }
+// @computed
+// private get editingMetadata() {
+// return BoolCast(this.props.Document.editingMetadata);
+// }
- private set editingMetadata(value: boolean) {
- this.props.Document.editingMetadata = value;
- }
+// private set editingMetadata(value: boolean) {
+// this.props.Document.editingMetadata = value;
+// }
- @computed
- private get persistent() {
- return BoolCast(this.props.Document.persistent);
- }
+// @computed
+// private get persistent() {
+// return BoolCast(this.props.Document.persistent);
+// }
- private set persistent(value: boolean) {
- this.props.Document.persistent = value;
- }
+// private set persistent(value: boolean) {
+// this.props.Document.persistent = value;
+// }
- handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
- runInAction(() => {
- this.uploading = true;
- this.phase = 'Initializing download...';
- });
+// handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+// runInAction(() => {
+// this.uploading = true;
+// this.phase = 'Initializing download...';
+// });
- const docs: Doc[] = [];
+// const docs: Doc[] = [];
- const files = e.target.files;
- if (!files || files.length === 0) return;
+// const files = e.target.files;
+// if (!files || files.length === 0) return;
- const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0];
+// const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0];
- const validated: File[] = [];
- for (let i = 0; i < files.length; i++) {
- const file = files.item(i);
- if (file && !unsupported.includes(file.type)) {
- const ext = extname(file.name).toLowerCase();
- if (AcceptableMedia.imageFormats.includes(ext)) {
- validated.push(file);
- }
- }
- }
+// const validated: File[] = [];
+// for (let i = 0; i < files.length; i++) {
+// const file = files.item(i);
+// if (file && !unsupported.includes(file.type)) {
+// const ext = extname(file.name).toLowerCase();
+// if (AcceptableMedia.imageFormats.includes(ext)) {
+// validated.push(file);
+// }
+// }
+// }
- runInAction(() => {
- this.quota = validated.length;
- this.completed = 0;
- });
+// runInAction(() => {
+// this.quota = validated.length;
+// this.completed = 0;
+// });
- const sizes: number[] = [];
- const modifiedDates: number[] = [];
+// const sizes: number[] = [];
+// const modifiedDates: number[] = [];
- runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`));
+// runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`));
- const batched = BatchedArray.from(validated, { batchSize: 15 });
- const uploads = await batched.batchedMapAsync<Upload.FileResponse<Upload.ImageInformation>>(async (batch, collector) => {
- batch.forEach(file => {
- sizes.push(file.size);
- modifiedDates.push(file.lastModified);
- });
- collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch.map(file =>({file})))));
- runInAction(() => (this.completed += batch.length));
- });
+// const batched = BatchedArray.from(validated, { batchSize: 15 });
+// const uploads = await batched.batchedMapAsync<Upload.FileResponse<Upload.ImageInformation>>(async (batch, collector) => {
+// batch.forEach(file => {
+// sizes.push(file.size);
+// modifiedDates.push(file.lastModified);
+// });
+// collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch.map(file => ({ file })))));
+// runInAction(() => (this.completed += batch.length));
+// });
- await Promise.all(
- uploads.map(async response => {
- const {
- source: { type },
- result,
- } = response;
- if (result instanceof Error) {
- return;
- }
- const { accessPaths, exifData } = result;
- const path = Utils.prepend(accessPaths.agnostic.client);
- const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 }));
- const { data, error } = exifData;
- if (document) {
- Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data });
- docs.push(document);
- }
- })
- );
+// await Promise.all(
+// uploads.map(async response => {
+// const {
+// source: { mimetype },
+// result,
+// } = response;
+// if (result instanceof Error) {
+// return;
+// }
+// const { accessPaths, exifData } = result;
+// const path = Utils.prepend(accessPaths.agnostic.client);
+// const document = mimetype && (await DocUtils.DocumentFromType(mimetype, path, { _width: 300 }));
+// const { data, error } = exifData;
+// if (document) {
+// Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data });
+// docs.push(document);
+// }
+// })
+// );
- for (let i = 0; i < docs.length; i++) {
- const doc = docs[i];
- doc.size = sizes[i];
- doc.modified = modifiedDates[i];
- this.entries.forEach(entry => {
- const target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
- target[entry.key] = entry.value;
- });
- }
+// for (let i = 0; i < docs.length; i++) {
+// const doc = docs[i];
+// doc.size = sizes[i];
+// doc.modified = modifiedDates[i];
+// this.entries.forEach(entry => {
+// const target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
+// target[entry.key] = entry.value;
+// });
+// }
- const doc = this.props.Document;
- const height: number = NumCast(doc.height) || 0;
- const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
- const options: DocumentOptions = {
- title: `Import of ${directory}`,
- _width: 1105,
- _height: 500,
- _chromeHidden: true,
- x: NumCast(doc.x),
- y: NumCast(doc.y) + offset,
- };
- const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
- if (parent?.rootDoc.type === DocumentType.COL) {
- let importContainer: Doc;
- if (docs.length < 50) {
- importContainer = Docs.Create.MasonryDocument(docs, options);
- } else {
- const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')];
- importContainer = Docs.Create.SchemaDocument(headers, docs, options);
- }
- runInAction(() => (this.phase = 'External: uploading files to Google Photos...'));
- await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
- Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer);
- !this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
- DocumentManager.Instance.showDocument(importContainer, { willZoomCentered: true });
- }
+// const doc = this.props.Document;
+// const height: number = NumCast(doc.height) || 0;
+// const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
+// const options: DocumentOptions = {
+// title: `Import of ${directory}`,
+// _width: 1105,
+// _height: 500,
+// _chromeHidden: true,
+// x: NumCast(doc.x),
+// y: NumCast(doc.y) + offset,
+// };
+// const parent = this.props.DocumentView?.()._props.docViewPath().lastElement();
+// if (parent?.Document.type === DocumentType.COL) {
+// let importContainer: Doc;
+// if (docs.length < 50) {
+// importContainer = Docs.Create.MasonryDocument(docs, options);
+// } else {
+// const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')];
+// importContainer = Docs.Create.SchemaDocument(headers, docs, options);
+// }
+// runInAction(() => (this.phase = 'External: uploading files to Google Photos...'));
+// await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
+// Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer);
+// !this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
+// DocumentManager.Instance.showDocument(importContainer, { willZoomCentered: true });
+// }
- runInAction(() => {
- this.uploading = false;
- this.quota = 1;
- this.completed = 0;
- });
- };
+// runInAction(() => {
+// this.uploading = false;
+// this.quota = 1;
+// this.completed = 0;
+// });
+// };
- componentDidMount() {
- this.selector.current!.setAttribute('directory', '');
- this.selector.current!.setAttribute('webkitdirectory', '');
- this.disposer = reaction(
- () => this.completed,
- completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`))
- );
- }
+// componentDidMount() {
+// this.selector.current!.setAttribute('directory', '');
+// this.selector.current!.setAttribute('webkitdirectory', '');
+// this.disposer = reaction(
+// () => this.completed,
+// completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`))
+// );
+// }
- componentWillUnmount() {
- this.disposer && this.disposer();
- }
+// componentWillUnmount() {
+// this.disposer && this.disposer();
+// }
- @action
- preserveCentering = (rect: ContentRect) => {
- const bounds = rect.offset!;
- if (bounds.width === 0 || bounds.height === 0) {
- return;
- }
- const offset = this.dimensions / 2;
- this.left = bounds.width / 2 - offset;
- this.top = bounds.height / 2 - offset;
- };
+// @action
+// preserveCentering = (rect: ContentRect) => {
+// const bounds = rect.offset!;
+// if (bounds.width === 0 || bounds.height === 0) {
+// return;
+// }
+// const offset = this.dimensions / 2;
+// this.left = bounds.width / 2 - offset;
+// this.top = bounds.height / 2 - offset;
+// };
- @action
- addMetadataEntry = async () => {
- const entryDoc = new Doc();
- entryDoc.checked = false;
- entryDoc.key = keyPlaceholder;
- entryDoc.value = valuePlaceholder;
- Doc.AddDocToList(this.props.Document, 'data', entryDoc);
- };
+// @action
+// addMetadataEntry = async () => {
+// const entryDoc = new Doc();
+// entryDoc.checked = false;
+// entryDoc.key = keyPlaceholder;
+// entryDoc.value = valuePlaceholder;
+// Doc.AddDocToList(this.props.Document, 'data', entryDoc);
+// };
- @action
- remove = async (entry: ImportMetadataEntry) => {
- const metadata = await DocListCastAsync(this.props.Document.data);
- if (metadata) {
- let index = this.entries.indexOf(entry);
- if (index !== -1) {
- runInAction(() => this.entries.splice(index, 1));
- index = metadata.indexOf(entry.props.Document);
- if (index !== -1) {
- metadata.splice(index, 1);
- }
- }
- }
- };
+// @action
+// remove = async (entry: ImportMetadataEntry) => {
+// const metadata = await DocListCastAsync(this.props.Document.data);
+// if (metadata) {
+// let index = this.entries.indexOf(entry);
+// if (index !== -1) {
+// runInAction(() => this.entries.splice(index, 1));
+// index = metadata.indexOf(entry.props.Document);
+// if (index !== -1) {
+// metadata.splice(index, 1);
+// }
+// }
+// }
+// };
- render() {
- const dimensions = 50;
- const entries = DocListCast(this.props.Document.data);
- const isEditing = this.editingMetadata;
- const completed = this.completed;
- const quota = this.quota;
- const uploading = this.uploading;
- const showRemoveLabel = this.removeHover;
- const persistent = this.persistent;
- let percent = `${(completed / quota) * 100}`;
- percent = percent.split('.')[0];
- percent = percent.startsWith('100') ? '99' : percent;
- const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
- const message = <span className={'phase'}>{this.phase}</span>;
- const centerPiece = this.phase.includes('Google Photos') ? (
- <img
- src={'/assets/google_photos.png'}
- style={{
- transition: '0.4s opacity ease',
- width: 30,
- height: 30,
- opacity: uploading ? 1 : 0,
- pointerEvents: 'none',
- position: 'absolute',
- left: 12,
- top: this.top + 10,
- fontSize: 18,
- color: 'white',
- marginLeft: this.left + marginOffset,
- }}
- />
- ) : (
- <div
- style={{
- transition: '0.4s opacity ease',
- opacity: uploading ? 1 : 0,
- pointerEvents: 'none',
- position: 'absolute',
- left: 10,
- top: this.top + 12.3,
- fontSize: 18,
- color: 'white',
- marginLeft: this.left + marginOffset,
- }}>
- {percent}%
- </div>
- );
- return (
- <Measure offset onResize={this.preserveCentering}>
- {({ measureRef }) => (
- <div ref={measureRef} style={{ width: '100%', height: '100%', pointerEvents: 'all' }}>
- {message}
- <input
- id={'selector'}
- ref={this.selector}
- onChange={this.handleSelection}
- type="file"
- style={{
- position: 'absolute',
- display: 'none',
- }}
- />
- <label
- htmlFor={'selector'}
- style={{
- opacity: isEditing ? 0 : 1,
- pointerEvents: isEditing ? 'none' : 'all',
- transition: '0.4s ease opacity',
- }}>
- <div
- style={{
- width: dimensions,
- height: dimensions,
- borderRadius: '50%',
- background: 'black',
- position: 'absolute',
- left: this.left,
- top: this.top,
- }}
- />
- <div
- style={{
- position: 'absolute',
- left: this.left + 8,
- top: this.top + 10,
- opacity: uploading ? 0 : 1,
- transition: '0.4s opacity ease',
- }}>
- <FontAwesomeIcon icon={'cloud-upload-alt'} color="#FFFFFF" size={'2x'} />
- </div>
- <img
- style={{
- width: 80,
- height: 80,
- transition: '0.4s opacity ease',
- opacity: uploading ? 0.7 : 0,
- position: 'absolute',
- top: this.top - 15,
- left: this.left - 15,
- }}
- src={'/assets/loading.gif'}></img>
- </label>
- <input
- type={'checkbox'}
- onChange={e => runInAction(() => (this.persistent = e.target.checked))}
- style={{
- margin: 0,
- position: 'absolute',
- left: 10,
- bottom: 10,
- opacity: isEditing || uploading ? 0 : 1,
- transition: '0.4s opacity ease',
- pointerEvents: isEditing || uploading ? 'none' : 'all',
- }}
- checked={this.persistent}
- onPointerEnter={action(() => (this.removeHover = true))}
- onPointerLeave={action(() => (this.removeHover = false))}
- />
- <p
- style={{
- position: 'absolute',
- left: 27,
- bottom: 8.4,
- fontSize: 12,
- opacity: showRemoveLabel ? 1 : 0,
- transition: '0.4s opacity ease',
- }}>
- Template will be <span style={{ textDecoration: 'underline', textDecorationColor: persistent ? 'green' : 'red', color: persistent ? 'green' : 'red' }}>{persistent ? 'kept' : 'removed'}</span> after upload
- </p>
- {centerPiece}
- <div
- style={{
- position: 'absolute',
- top: 10,
- right: 10,
- borderRadius: '50%',
- width: 25,
- height: 25,
- background: 'black',
- pointerEvents: uploading ? 'none' : 'all',
- opacity: uploading ? 0 : 1,
- transition: '0.4s opacity ease',
- }}
- title={isEditing ? 'Back to Upload' : 'Add Metadata'}
- onClick={action(() => (this.editingMetadata = !this.editingMetadata))}
- />
- <FontAwesomeIcon
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- right: isEditing ? 14 : 15,
- top: isEditing ? 15.4 : 16,
- opacity: uploading ? 0 : 1,
- transition: '0.4s opacity ease',
- }}
- icon={isEditing ? 'cloud-upload-alt' : 'tag'}
- color="#FFFFFF"
- size={'1x'}
- />
- <div
- style={{
- transition: '0.4s ease opacity',
- width: '100%',
- height: '100%',
- pointerEvents: isEditing ? 'all' : 'none',
- opacity: isEditing ? 1 : 0,
- overflowY: 'scroll',
- }}>
- <div
- style={{
- borderRadius: '50%',
- width: 25,
- height: 25,
- marginLeft: 10,
- position: 'absolute',
- right: 41,
- top: 10,
- }}
- title={'Add Metadata Entry'}
- onClick={this.addMetadataEntry}>
- <FontAwesomeIcon
- style={{
- pointerEvents: 'none',
- marginLeft: 6.4,
- marginTop: 5.2,
- }}
- icon={'plus'}
- size={'1x'}
- />
- </div>
- <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }}>Add metadata to your import...</p>
- <hr style={{ margin: '6px 10px 12px 10px' }} />
- {entries.map(doc => (
- <ImportMetadataEntry
- Document={doc}
- key={doc[Id]}
- remove={this.remove}
- ref={el => {
- if (el) this.entries.push(el);
- }}
- next={this.addMetadataEntry}
- />
- ))}
- </div>
- </div>
- )}
- </Measure>
- );
- }
-}
+// render() {
+// const dimensions = 50;
+// const entries = DocListCast(this.props.Document.data);
+// const isEditing = this.editingMetadata;
+// const completed = this.completed;
+// const quota = this.quota;
+// const uploading = this.uploading;
+// const showRemoveLabel = this.removeHover;
+// const persistent = this.persistent;
+// let percent = `${(completed / quota) * 100}`;
+// percent = percent.split('.')[0];
+// percent = percent.startsWith('100') ? '99' : percent;
+// const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
+// const message = <span className={'phase'}>{this.phase}</span>;
+// const centerPiece = this.phase.includes('Google Photos') ? (
+// <img
+// src={'/assets/google_photos.png'}
+// style={{
+// transition: '0.4s opacity ease',
+// width: 30,
+// height: 30,
+// opacity: uploading ? 1 : 0,
+// pointerEvents: 'none',
+// position: 'absolute',
+// left: 12,
+// top: this.top + 10,
+// fontSize: 18,
+// color: 'white',
+// marginLeft: this.left + marginOffset,
+// }}
+// />
+// ) : (
+// <div
+// style={{
+// transition: '0.4s opacity ease',
+// opacity: uploading ? 1 : 0,
+// pointerEvents: 'none',
+// position: 'absolute',
+// left: 10,
+// top: this.top + 12.3,
+// fontSize: 18,
+// color: 'white',
+// marginLeft: this.left + marginOffset,
+// }}>
+// {percent}%
+// </div>
+// );
+// return (
+// <Measure offset onResize={this.preserveCentering}>
+// {({ measureRef }) => (
+// <div ref={measureRef} style={{ width: '100%', height: '100%', pointerEvents: 'all' }}>
+// {message}
+// <input
+// id={'selector'}
+// ref={this.selector}
+// onChange={this.handleSelection}
+// type="file"
+// style={{
+// position: 'absolute',
+// display: 'none',
+// }}
+// />
+// <label
+// htmlFor={'selector'}
+// style={{
+// opacity: isEditing ? 0 : 1,
+// pointerEvents: isEditing ? 'none' : 'all',
+// transition: '0.4s ease opacity',
+// }}>
+// <div
+// style={{
+// width: dimensions,
+// height: dimensions,
+// borderRadius: '50%',
+// background: 'black',
+// position: 'absolute',
+// left: this.left,
+// top: this.top,
+// }}
+// />
+// <div
+// style={{
+// position: 'absolute',
+// left: this.left + 8,
+// top: this.top + 10,
+// opacity: uploading ? 0 : 1,
+// transition: '0.4s opacity ease',
+// }}>
+// <FontAwesomeIcon icon={'cloud-upload-alt'} color="#FFFFFF" size={'2x'} />
+// </div>
+// <img
+// style={{
+// width: 80,
+// height: 80,
+// transition: '0.4s opacity ease',
+// opacity: uploading ? 0.7 : 0,
+// position: 'absolute',
+// top: this.top - 15,
+// left: this.left - 15,
+// }}
+// src={'/assets/loading.gif'}></img>
+// </label>
+// <input
+// type={'checkbox'}
+// onChange={e => runInAction(() => (this.persistent = e.target.checked))}
+// style={{
+// margin: 0,
+// position: 'absolute',
+// left: 10,
+// bottom: 10,
+// opacity: isEditing || uploading ? 0 : 1,
+// transition: '0.4s opacity ease',
+// pointerEvents: isEditing || uploading ? 'none' : 'all',
+// }}
+// checked={this.persistent}
+// onPointerEnter={action(() => (this.removeHover = true))}
+// onPointerLeave={action(() => (this.removeHover = false))}
+// />
+// <p
+// style={{
+// position: 'absolute',
+// left: 27,
+// bottom: 8.4,
+// fontSize: 12,
+// opacity: showRemoveLabel ? 1 : 0,
+// transition: '0.4s opacity ease',
+// }}>
+// Template will be <span style={{ textDecoration: 'underline', textDecorationColor: persistent ? 'green' : 'red', color: persistent ? 'green' : 'red' }}>{persistent ? 'kept' : 'removed'}</span> after upload
+// </p>
+// {centerPiece}
+// <div
+// style={{
+// position: 'absolute',
+// top: 10,
+// right: 10,
+// borderRadius: '50%',
+// width: 25,
+// height: 25,
+// background: 'black',
+// pointerEvents: uploading ? 'none' : 'all',
+// opacity: uploading ? 0 : 1,
+// transition: '0.4s opacity ease',
+// }}
+// title={isEditing ? 'Back to Upload' : 'Add Metadata'}
+// onClick={action(() => (this.editingMetadata = !this.editingMetadata))}
+// />
+// <FontAwesomeIcon
+// style={{
+// pointerEvents: 'none',
+// position: 'absolute',
+// right: isEditing ? 14 : 15,
+// top: isEditing ? 15.4 : 16,
+// opacity: uploading ? 0 : 1,
+// transition: '0.4s opacity ease',
+// }}
+// icon={isEditing ? 'cloud-upload-alt' : 'tag'}
+// color="#FFFFFF"
+// size={'1x'}
+// />
+// <div
+// style={{
+// transition: '0.4s ease opacity',
+// width: '100%',
+// height: '100%',
+// pointerEvents: isEditing ? 'all' : 'none',
+// opacity: isEditing ? 1 : 0,
+// overflowY: 'scroll',
+// }}>
+// <div
+// style={{
+// borderRadius: '50%',
+// width: 25,
+// height: 25,
+// marginLeft: 10,
+// position: 'absolute',
+// right: 41,
+// top: 10,
+// }}
+// title={'Add Metadata Entry'}
+// onClick={this.addMetadataEntry}>
+// <FontAwesomeIcon
+// style={{
+// pointerEvents: 'none',
+// marginLeft: 6.4,
+// marginTop: 5.2,
+// }}
+// icon={'plus'}
+// size={'1x'}
+// />
+// </div>
+// <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }}>Add metadata to your import...</p>
+// <hr style={{ margin: '6px 10px 12px 10px' }} />
+// {entries.map(doc => (
+// <ImportMetadataEntry
+// Document={doc}
+// key={doc[Id]}
+// remove={this.remove}
+// ref={el => {
+// if (el) this.entries.push(el);
+// }}
+// next={this.addMetadataEntry}
+// />
+// ))}
+// </div>
+// </div>
+// )}
+// </Measure>
+// );
+// }
+// }
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 45d8c0c63..58a09b9c9 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -1,10 +1,10 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { EditableView } from "../../views/EditableView";
-import { action, computed } from "mobx";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Doc } from "../../../fields/Doc";
-import { StrCast, BoolCast } from "../../../fields/Types";
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { EditableView } from '../../views/EditableView';
+import { action, computed } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Doc } from '../../../fields/Doc';
+import { StrCast, BoolCast } from '../../../fields/Types';
interface KeyValueProps {
Document: Doc;
@@ -12,19 +12,18 @@ interface KeyValueProps {
next: () => void;
}
-export const keyPlaceholder = "Key";
-export const valuePlaceholder = "Value";
+export const keyPlaceholder = 'Key';
+export const valuePlaceholder = 'Value';
@observer
export default class ImportMetadataEntry extends React.Component<KeyValueProps> {
-
private keyRef = React.createRef<EditableView>();
private valueRef = React.createRef<EditableView>();
private checkRef = React.createRef<HTMLInputElement>();
@computed
public get valid() {
- return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
+ return this.key.length > 0 && this.key !== keyPlaceholder && this.value.length > 0 && this.value !== valuePlaceholder;
}
@computed
@@ -66,7 +65,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
this.valueRef.current && this.valueRef.current.setIsFocused(true);
this.key.length === 0 && (this.key = keyPlaceholder);
return true;
- }
+ };
@action
updateValue = (newValue: string, shiftDown: boolean) => {
@@ -75,68 +74,45 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
this.value.length > 0 && shiftDown && this.props.next();
this.value.length === 0 && (this.value = valuePlaceholder);
return true;
- }
+ };
render() {
const keyValueStyle: React.CSSProperties = {
paddingLeft: 10,
- width: "50%",
+ width: '50%',
opacity: this.valid ? 1 : 0.5,
};
return (
<div
style={{
- display: "flex",
- flexDirection: "row",
+ display: 'flex',
+ flexDirection: 'row',
paddingBottom: 5,
paddingRight: 5,
- justifyContent: "center",
- 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}
- />
+ justifyContent: 'center',
+ 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} />
</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={true} />
</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,
- transform: "rotate(45deg)"
+ transform: 'rotate(45deg)',
}}
/>
</div>
</div>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index be885312d..a2f5826fe 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Utils } from '../../Utils';
import './InteractionUtils.scss';
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 608184596..ccb3c6b98 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { action, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
import { DirectLinks } from '../../fields/DocSymbols';
@@ -22,9 +22,9 @@ import { ScriptingGlobals } from './ScriptingGlobals';
*/
export class LinkManager {
@observable static _instance: LinkManager;
- @observable static userLinkDBs: Doc[] = [];
- @observable public static currentLink: Opt<Doc>;
- @observable public static currentLinkAnchor: Opt<Doc>;
+ @observable.shallow userLinkDBs: Doc[] = [];
+ @observable public static currentLink: Opt<Doc> = undefined;
+ @observable public static currentLinkAnchor: Opt<Doc> = undefined;
public static get Instance() {
return LinkManager._instance;
}
@@ -32,21 +32,20 @@ export class LinkManager {
public static Links(doc: Doc | undefined) {
return doc ? LinkManager.Instance.getAllRelatedLinks(doc) : [];
}
- public static addLinkDB = async (linkDb: any) => {
+ public addLinkDB = async (linkDb: any) => {
await Promise.all(
((await DocListCastAsync(linkDb.data)) ?? []).map(link =>
// makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
[PromiseValue(link?.link_anchor_1), PromiseValue(link?.link_anchor_2)]
)
);
- LinkManager.userLinkDBs.push(linkDb);
+ this.userLinkDBs.push(linkDb);
};
public static AutoKeywords = 'keywords:Usages';
- static _links: Doc[] = [];
constructor() {
+ makeObservable(this);
LinkManager._instance = this;
this.createlink_relationshipLists();
- LinkManager.userLinkDBs = [];
// since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
// Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
// Then add the link to the anchor protos.
@@ -85,7 +84,6 @@ export class LinkManager {
);
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- LinkManager._links.push(...DocListCast(userLinkDBDoc.data));
const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
if (userLinkDBDoc.data) {
observe(
@@ -124,7 +122,7 @@ export class LinkManager {
}
};
observe(
- LinkManager.userLinkDBs,
+ this.userLinkDBs,
change => {
switch (change.type as any) {
case 'splice':
@@ -135,8 +133,8 @@ export class LinkManager {
},
true
);
- runInAction(() => (FieldLoader.ServerLoadStatus.message = 'links'));
- LinkManager.addLinkDB(Doc.LinkDBDoc());
+ FieldLoader.ServerLoadStatus.message = 'links';
+ this.addLinkDB(Doc.LinkDBDoc());
}
public createlink_relationshipLists = () => {
@@ -163,8 +161,8 @@ export class LinkManager {
public getAllRelatedLinks(anchor: Doc) {
return this.relatedLinker(anchor);
} // finds all links that contain the given anchor
- public getAllDirectLinks(anchor: Doc): Doc[] {
- return Array.from(Doc.GetProto(anchor)[DirectLinks]);
+ public getAllDirectLinks(anchor?: Doc): Doc[] {
+ return anchor ? Array.from(Doc.GetProto(anchor)[DirectLinks]) : [];
} // finds all links that contain the given anchor
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 4dd2fcd35..865f8bc02 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { Networking } from '../Network';
import { CurrentUserUtils } from './CurrentUserUtils';
export class PingManager {
@@ -33,6 +33,7 @@ export class PingManager {
private _interval: NodeJS.Timeout | null = null;
INTERVAL_SECONDS = 1;
constructor() {
+ makeObservable(this);
PingManager._instance = this;
this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
}
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index c8940194c..f96d8a5df 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
@@ -23,10 +23,11 @@ export class RTFMarkup extends React.Component<{}> {
constructor(props: {}) {
super(props);
+ makeObservable(this);
RTFMarkup.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined;
+ @observable _stats: { [key: string]: any } | undefined = undefined;
/**
* @returns the main interface of the SharingManager.
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index d99630f82..b881f18b4 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { Doc, IdToDoc } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
@@ -19,6 +19,7 @@ export class ReplayMovements {
return ReplayMovements._instance;
}
constructor() {
+ makeObservable(this);
// init the global instance
ReplayMovements._instance = this;
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 70c2e3842..dbb994dbd 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -120,6 +120,7 @@ class ScriptingCompilerHost {
}
return undefined;
}
+
// getDefaultLibFileName(options: ts.CompilerOptions): string {
getDefaultLibFileName(options: any): string {
return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
@@ -159,7 +160,7 @@ class ScriptingCompilerHost {
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.SourceFile>;
+ transformer: ts.TransformerFactory<ts.Node>;
getVars?: () => { [name: string]: Field };
};
export interface ScriptOptions {
@@ -219,7 +220,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
});
- script = printer.printFile(transformed[0]);
+ script = printer.printFile(transformed[0].getSourceFile());
}
result.dispose();
}
@@ -247,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
host.writeFile('file.ts', funcScript);
- if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
+ if (typecheck && false) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
const program = ts.createProgram(['file.ts'], {}, host);
const testResult = program.emit();
const outputText = host.readFile('file.js');
@@ -264,6 +265,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
}
ScriptingGlobals.add(CompileScript);
-ScriptingGlobals.add(function runScript(self: Doc, script: ScriptField) {
- return script?.script.run({ this: self, self: self }).result;
+ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
+ return script?.script.run({ this: doc }).result;
});
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index d4620ae3f..785526ab3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> {
onWheel = (e: React.WheelEvent) => {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 560d6b30f..e51770c25 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -9,7 +9,7 @@ import { StrCast } from '../../fields/Types';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
- export function SearchCollection(rootDoc: Opt<Doc>, query: string) {
+ export function SearchCollection(collectionDoc: Opt<Doc>, query: string) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = [
'x',
@@ -48,8 +48,8 @@ export namespace SearchUtil {
query = query.toLowerCase();
const results = new Map<Doc, string[]>();
- if (rootDoc) {
- const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]);
+ if (collectionDoc) {
+ const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
const docIDs: String[] = [];
SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
const dtype = StrCast(doc.type) as DocumentType;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index f7e6fa2dc..07bbde36c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
@@ -10,86 +10,70 @@ import { LinkManager } from './LinkManager';
import { ScriptingGlobals } from './ScriptingGlobals';
import { UndoManager } from './UndoManager';
-export namespace SelectionManager {
- class Manager {
- @observable SelectedViews: DocumentView[] = [];
- @observable IsDragging: boolean = false;
- @observable SelectedSchemaDocument: Doc | undefined;
-
- @action
- SelectSchemaViewDoc(doc: Opt<Doc>) {
- manager.SelectedSchemaDocument = doc;
- }
- @action
- SelectView(docView: DocumentView, extendSelection: boolean): void {
- if (!docView.SELECTED) {
- if (!extendSelection) this.DeselectAll();
- manager.SelectedViews.push(docView);
- docView.SELECTED = true;
- docView.props.whenChildContentsActiveChanged(true);
- }
- }
- @action
- DeselectView(docView?: DocumentView): void {
- if (docView && manager.SelectedViews.includes(docView)) {
- docView.SELECTED = false;
- manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
- docView.props.whenChildContentsActiveChanged(false);
- }
- }
- @action
- DeselectAll(): void {
- LinkManager.currentLink = undefined;
- LinkManager.currentLinkAnchor = undefined;
- manager.SelectedSchemaDocument = undefined;
- manager.SelectedViews.forEach(dv => {
- dv.SELECTED = false;
- dv.props.whenChildContentsActiveChanged(false);
- });
- manager.SelectedViews.length = 0;
- }
+export class SelectionManager {
+ private static _manager: SelectionManager;
+ private static get Instance() {
+ return SelectionManager._manager ?? new SelectionManager();
}
- const manager = new Manager();
+ @observable.shallow SelectedViews: DocumentView[] = [];
+ @observable IsDragging: boolean = false;
+ @observable SelectedSchemaDocument: Doc | undefined = undefined;
- export function DeselectView(docView?: DocumentView): void {
- manager.DeselectView(docView);
- }
- export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void {
- if (!docView) DeselectAll();
- else manager.SelectView(docView, ctrlPressed);
- }
- export function SelectSchemaViewDoc(document: Opt<Doc>, deselectAllFirst?: boolean): void {
- if (deselectAllFirst) manager.DeselectAll();
- manager.SelectSchemaViewDoc(document);
+ private constructor() {
+ SelectionManager._manager = this;
+ makeObservable(this);
}
- export function IsSelected(doc?: Doc): boolean {
- return Array.from(doc?.[DocViews] ?? []).some(dv => dv?.SELECTED);
- }
+ @action
+ public static SelectSchemaViewDoc = (doc: Opt<Doc>, deselectAllFirst?: boolean) => {
+ if (deselectAllFirst) this.DeselectAll();
+ this.Instance.SelectedSchemaDocument = doc;
+ };
- export function DeselectAll(except?: Doc): void {
- const found = manager.SelectedViews.find(dv => dv.Document === except);
- manager.DeselectAll();
- if (found) manager.SelectView(found, false);
- }
+ public static SelectView = action((docView: DocumentView | undefined, extendSelection: boolean): void => {
+ if (!docView) this.DeselectAll();
+ else if (!docView.SELECTED) {
+ if (!extendSelection) this.DeselectAll();
+ this.Instance.SelectedViews.push(docView);
+ docView.SELECTED = true;
+ docView._props.whenChildContentsActiveChanged(true);
+ }
+ });
- export function Views(): Array<DocumentView> {
- return manager.SelectedViews;
- }
- export function SelectedSchemaDoc(): Doc | undefined {
- return manager.SelectedSchemaDocument;
- }
- export function Docs(): Doc[] {
- return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._type_collection !== CollectionViewType.Docking);
- }
+ public static DeselectView = action((docView?: DocumentView): void => {
+ if (docView && this.Instance.SelectedViews.includes(docView)) {
+ docView.SELECTED = false;
+ this.Instance.SelectedViews.splice(this.Instance.SelectedViews.indexOf(docView), 1);
+ docView._props.whenChildContentsActiveChanged(false);
+ }
+ });
+
+ public static DeselectAll = (except?: Doc): void => {
+ const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
+ LinkManager.currentLink = undefined;
+ LinkManager.currentLinkAnchor = undefined;
+ runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
+ this.Instance.SelectedViews.forEach(dv => {
+ dv.SELECTED = false;
+ dv._props.whenChildContentsActiveChanged(false);
+ });
+ this.Instance.SelectedViews.length = 0;
+ if (found) this.SelectView(found, false);
+ };
+
+ public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.SELECTED);
+ public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
+ public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
+ public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
}
+
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;
+ return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
}
- let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+ let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
});
ScriptingGlobals.add(function deselectAll() {
@@ -114,8 +98,8 @@ ScriptingGlobals.add(function redo() {
return UndoManager.Redo();
});
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views()
- .map(dv => dv.props.Document)
- .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
+ const docs = SelectionManager.Views.map(dv => dv.Document).filter(
+ d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
+ );
return docs.length ? new List(docs) : prevValue;
});
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 76037a7e9..8daa69890 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,6 +1,5 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr';
import { Field } from '../../fields/Doc';
-import { ClientUtils } from './ClientUtils';
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 08dbaac5d..c8df9182d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
@@ -33,10 +33,11 @@ export class ServerStats extends React.Component<{}> {
constructor(props: {}) {
super(props);
+ makeObservable(this);
ServerStats.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined;
+ @observable _stats: { [key: string]: any } | undefined = undefined;
/**
* @returns the main interface of the SharingManager.
@@ -56,9 +57,7 @@ export class ServerStats extends React.Component<{}> {
<br />
<span>Active users:{this._stats?.socketMap.length}</span>
- {this._stats?.socketMap.map((user: any) => (
- <p>{user.username}</p>
- ))}
+ {this._stats?.socketMap.map((user: any) => <p>{user.username}</p>)}
</div>
</div>
);
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index bca649bc3..dbfc48c63 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -1,4 +1,4 @@
-@import '../views/global/globalCssVariables';
+@import '../views/global/globalCssVariables.module';
.settings-interface {
//background-color: whitesmoke !important;
@@ -187,14 +187,12 @@
display: flex;
flex-direction: column;
-
.close-button {
position: absolute;
right: 2px;
top: 2px;
}
-
.settings-content {
padding: 10px;
width: 500px;
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 39c471970..ccf6fb820 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsGoogle } from 'react-icons/bs';
@@ -45,11 +45,12 @@ export class SettingsManager extends React.Component<{}> {
@observable private new_confirm = '';
@observable activeTab = 'Accounts';
- @observable public static propertiesWidth: number = 0;
- @observable public static headerBarHeight: number = 0;
+ @observable public propertiesWidth: number = 0;
+ @observable public headerBarHeight: number = 0;
constructor(props: {}) {
super(props);
+ makeObservable(this);
SettingsManager.Instance = this;
this.matchSystem();
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
@@ -116,7 +117,6 @@ export class SettingsManager extends React.Component<{}> {
});
@undoBatch
- @action
changeColorScheme = action((scheme: string) => {
Doc.UserDoc().userTheme = scheme;
switch (scheme) {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 8d59426ec..ac7de9872 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { concat, intersection } from 'lodash';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
@@ -67,8 +67,8 @@ export class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
@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; // the document being shared
- @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared
+ @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
@@ -119,6 +119,7 @@ export class SharingManager extends React.Component<{}> {
constructor(props: {}) {
super(props);
+ makeObservable(this);
SharingManager.Instance = this;
}
@@ -133,7 +134,7 @@ 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] !== '__guest__') {
+ 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);
@@ -162,7 +163,7 @@ export class SharingManager extends React.Component<{}> {
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.rootDoc);
+ 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) {
@@ -180,7 +181,7 @@ export class SharingManager extends React.Component<{}> {
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
- const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+ 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);
@@ -317,7 +318,7 @@ export class SharingManager extends React.Component<{}> {
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];
+ const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
return (
<span
className="focus-span"
@@ -444,7 +445,7 @@ export class SharingManager extends React.Component<{}> {
const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
- let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+ let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
if (this.myDocAcls) {
const newDocs: Doc[] = [];
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index fce43eef6..b8bd90983 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,68 +1,41 @@
-import { observable, action, runInAction } from 'mobx';
-import { Doc } from '../../fields/Doc';
+import { observable, action, runInAction, reaction, makeObservable } from 'mobx';
+import { Doc, Opt } from '../../fields/Doc';
-export namespace SnappingManager {
- class Manager {
- @observable ShiftKey = false;
- @observable CtrlKey = false;
- @observable IsDragging: boolean = false;
- @observable IsResizing: Doc | undefined;
- @observable CanEmbed: boolean = false;
- @observable public horizSnapLines: number[] = [];
- @observable public vertSnapLines: number[] = [];
- @action public clearSnapLines() {
- this.vertSnapLines = [];
- this.horizSnapLines = [];
- }
- @action public addSnapLines(horizLines: number[], vertLines: number[]) {
- this.horizSnapLines.push(...horizLines);
- this.vertSnapLines.push(...vertLines);
- }
+export class SnappingManager {
+ private static _manager: SnappingManager;
+ private static get Instance() {
+ return SnappingManager._manager ?? new SnappingManager();
}
- const manager = new Manager();
+ @observable _shiftKey = false;
+ @observable _ctrlKey = false;
+ @observable _isDragging: boolean = false;
+ @observable _isResizing: Doc | undefined = undefined;
+ @observable _canEmbed: boolean = false;
+ @observable _horizSnapLines: number[] = [];
+ @observable _vertSnapLines: number[] = [];
- export function clearSnapLines() {
- manager.clearSnapLines();
- }
- export function addSnapLines(horizLines: number[], vertLines: number[]) {
- manager.addSnapLines(horizLines, vertLines);
- }
- export function horizSnapLines() {
- return manager.horizSnapLines;
- }
- export function vertSnapLines() {
- return manager.vertSnapLines;
+ private constructor() {
+ SnappingManager._manager = this;
+ makeObservable(this);
}
- export function SetShiftKey(down: boolean) {
- runInAction(() => (manager.ShiftKey = down));
- }
- export function SetCtrlKey(down: boolean) {
- runInAction(() => (manager.CtrlKey = down));
- }
- export function SetIsDragging(dragging: boolean) {
- runInAction(() => (manager.IsDragging = dragging));
- }
- export function SetIsResizing(doc: Doc | undefined) {
- runInAction(() => (manager.IsResizing = doc));
- }
- export function SetCanEmbed(canEmbed: boolean) {
- runInAction(() => (manager.CanEmbed = canEmbed));
- }
- export function GetShiftKey() {
- return manager.ShiftKey;
- }
- export function GetCtrlKey() {
- return manager.CtrlKey;
- }
- export function GetIsDragging() {
- return manager.IsDragging;
- }
- export function GetIsResizing() {
- return manager.IsResizing;
- }
- export function GetCanEmbed() {
- return manager.CanEmbed;
- }
+ @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);
+ };
+
+ public static get HorizSnapLines() { return this.Instance._horizSnapLines; } // prettier-ignore
+ public static get VertSnapLines() { return this.Instance._vertSnapLines; } // prettier-ignore
+ public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
+ public static get CtrlKey() { return this.Instance._ctrlKey; } // 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 SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
+ public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // 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
}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index 0e56ee1bc..0b197cf9a 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
import { NumCast } from '../../fields/Types';
import { Doc, DocListCast } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -40,7 +40,7 @@ export class TrackMovements {
constructor() {
// init the global instance
TrackMovements._instance = this;
-
+ makeObservable(this);
// init the instance variables
this.currentPresentation = TrackMovements.NULL_PRESENTATION;
this.tracking = false;
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
index cd6a1d934..d82d7fdeb 100644
--- a/src/client/util/reportManager/ReportManager.scss
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -1,4 +1,4 @@
-@import '../../views/global/globalCssVariables';
+@import '../../views/global/globalCssVariables.module';
// header
@@ -360,5 +360,8 @@
padding: 4px 10px;
font-size: 10px;
border-radius: 32px;
- transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+ transition:
+ background-color 0.2s ease,
+ color 0.2s ease,
+ border-color 0.2s ease;
}
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index b25d51b41..0c49aeed4 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,13 +1,11 @@
import * as React from 'react';
-import v4 = require('uuid/v4');
+import * as uuid from 'uuid';
import '.././SettingsManager.scss';
import './ReportManager.scss';
-import Dropzone from 'react-dropzone';
import ReactLoading from 'react-loading';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
-import { AiOutlineUpload } from 'react-icons/ai';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { Issue } from './reportManagerSchema';
import { observer } from 'mobx-react';
@@ -105,6 +103,7 @@ export class ReportManager extends React.Component<{}> {
constructor(props: {}) {
super(props);
+ makeObservable(this);
ReportManager.Instance = this;
// initializing Github connection
@@ -156,7 +155,7 @@ export class ReportManager extends React.Component<{}> {
* @param files uploaded files
*/
private onDrop = (files: File[]) => {
- this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: v4(), file }))] });
+ this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: uuid.v4(), file }))] });
};
/**
@@ -338,7 +337,7 @@ export class ReportManager extends React.Component<{}> {
multiple
onChange={e => {
if (!e.target.files) return;
- this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: v4(), file }))] });
+ this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: uuid.v4(), file }))] });
}}
/>
{this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index e870c073d..1e226bf6d 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -289,7 +289,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
</div>
</div>
)}
- <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
+ <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
</div>
);
};
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index b205a0f1e..2ebf673fe 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -1,5 +1,4 @@
-@import "./global/globalCssVariables";
-
+@import './global/globalCssVariables.module';
.antimodeMenu-cont {
position: absolute;
@@ -15,8 +14,13 @@
align-items: center;
gap: 3px;
+ &.expanded {
+ // Conditionally unset the height when the class is applied
+ height: auto;
+ }
+
&.with-rows {
- flex-direction: column
+ flex-direction: column;
}
.antimodeMenu-row {
@@ -26,8 +30,8 @@
.antimodeMenu-dragger {
height: 100%;
- transition: width .2s;
- background-image: url("https://logodix.com/logo/1020374.png");
+ transition: width 0.2s;
+ background-image: url('https://logodix.com/logo/1020374.png');
background-size: 90% 100%;
background-repeat: no-repeat;
background-position: left center;
@@ -63,4 +67,4 @@
background-color: #121212;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 16e76694d..db7e64deb 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,21 +1,25 @@
-import React = require('react');
-import { observable, action, runInAction } from 'mobx';
-import './AntimodeMenu.scss';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
+import { action, makeObservable, observable, runInAction } from 'mobx';
+import * as React from 'react';
import { SettingsManager } from '../util/SettingsManager';
+import './AntimodeMenu.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
export interface AntimodeMenuProps {}
/**
* This is an abstract class that serves as the base for a PDF-style or Marquee-style
* menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
*/
-export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Component<T, {}> {
+export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends ObservableReactComponent<T> {
protected _offsetY: number = 0;
protected _offsetX: number = 0;
protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
protected _dragging: boolean = false;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable protected _top: number = -300;
@observable protected _left: number = -300;
@observable protected _opacity: number = 0;
@@ -139,10 +143,12 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
};
- protected getElement(buttons: JSX.Element) {
+ protected getElement(buttons: JSX.Element, expanded: boolean = false) {
+ const containerClass = expanded ? 'antimodeMenu-cont expanded' : 'antimodeMenu-cont';
+
return (
<div
- className="antimodeMenu-cont"
+ className={containerClass}
onPointerLeave={this.pointerLeave}
onPointerEnter={this.pointerEntered}
ref={this._mainCont}
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
deleted file mode 100644
index c779ce8c4..000000000
--- a/src/client/views/AudioWaveform.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import React = require('react');
-import axios from 'axios';
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import Waveform from 'react-audio-waveform';
-import { Doc, NumListCast } from '../../fields/Doc';
-import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { Cast } from '../../fields/Types';
-import { numberRange } from '../../Utils';
-import './AudioWaveform.scss';
-import { Colors } from './global/globalEnums';
-
-/**
- * AudioWaveform
- *
- * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
- * Uses react-audio-waveform package.
- * Bins the audio data into audioBuckets which are passed to package to render the lines.
- * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
- */
-
-export interface AudioWaveformProps {
- duration: number; // length of media clip
- rawDuration: number; // length of underlying media data
- mediaPath: string;
- layoutDoc: Doc;
- clipStart: number;
- clipEnd: number;
- zoomFactor: number;
- PanelHeight: number;
- PanelWidth: number;
- fieldKey: string;
-}
-
-@observer
-export class AudioWaveform extends React.Component<AudioWaveformProps> {
- public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
-
- _disposer: IReactionDisposer | undefined;
-
- @computed get waveHeight() {
- return Math.max(50, this.props.PanelHeight);
- }
-
- @computed get clipStart() {
- return this.props.clipStart;
- }
- @computed get clipEnd() {
- return this.props.clipEnd;
- }
- @computed get zoomFactor() {
- return this.props.zoomFactor;
- }
-
- @computed get audioBuckets() {
- return 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;
-
- componentWillUnmount() {
- this._disposer?.();
- }
-
- componentDidMount() {
- this._disposer = reaction(
- () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
- ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
- if (!this.props.layoutDoc[fieldKey]) {
- // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
- const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec('number'));
- this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this.props.rawDuration) * waveform.length, (clipEnd / this.props.rawDuration) * waveform.length));
- setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
- }
- },
- { fireImmediately: true }
- );
- }
-
- // decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
- axios({ url: this.props.mediaPath, responseType: 'arraybuffer' }).then(response => {
- const context = new window.AudioContext();
- context.decodeAudioData(
- response.data,
- action(buffer => {
- const rawDecodedAudioData = buffer.getChannelData(0);
- const startInd = clipStart / this.props.rawDuration;
- const endInd = clipEnd / this.props.rawDuration;
- const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
- const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
-
- const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
- const brange = Array.from(Array(bucketDataSize));
- const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
- this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
- })
- );
- });
- };
-
- render() {
- return (
- <div className="audioWaveform">
- <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={Array.from(this.audioBuckets)} progressColor={Colors.MEDIUM_BLUE_ALT} />
- </div>
- );
- }
-}
diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
index 66d1bd63d..ca4e5f2bc 100644
--- a/src/client/views/ComponentDecorations.tsx
+++ b/src/client/views/ComponentDecorations.tsx
@@ -1,14 +1,14 @@
-import { observer } from "mobx-react";
-import { SelectionManager } from "../util/SelectionManager";
+import { observer } from 'mobx-react';
+import { SelectionManager } from '../util/SelectionManager';
import './ComponentDecorations.scss';
-import React = require("react");
+import * as React from 'react';
@observer
-export class ComponentDecorations extends React.Component<{ boundsTop: number, boundsLeft: number }, { value: string }> {
+export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> {
static Instance: ComponentDecorations;
render() {
- const seldoc = SelectionManager.Views().lastElement();
- return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? (null);
+ const seldoc = SelectionManager.Views.lastElement();
+ return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 588eff1d1..232362c5c 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module.scss';
.contextMenu-cont {
position: absolute;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index adefc7e9c..8dcdd80e5 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,40 +1,43 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import './ContextMenu.scss';
-import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
-import { Utils } from '../../Utils';
+import * as React from 'react';
import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
import { SettingsManager } from '../util/SettingsManager';
+import './ContextMenu.scss';
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
+import { ObservableReactComponent } from './ObservableReactComponent';
@observer
-export class ContextMenu extends React.Component {
+export class ContextMenu extends ObservableReactComponent<{}> {
static Instance: ContextMenu;
- @observable private _items: Array<ContextMenuProps> = [];
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable private _display: boolean = false;
- @observable private _searchString: string = '';
- @observable private _showSearch: boolean = false;
+ private _ignoreUp = false;
+ private _reactionDisposer?: IReactionDisposer;
+ private _defaultPrefix: string = '';
+ private _defaultItem: ((name: string) => void) | undefined;
+ private _onDisplay?: () => void = undefined;
+
+ @observable.shallow _items: ContextMenuProps[] = [];
+ @observable _pageX: number = 0;
+ @observable _pageY: number = 0;
+ @observable _display: boolean = false;
+ @observable _searchString: string = '';
+ @observable _showSearch: boolean = false;
// afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
- @observable private _yRelativeToTop: boolean = true;
- @observable selectedIndex = -1;
+ @observable _yRelativeToTop: boolean = true;
+ @observable _selectedIndex = -1;
- @observable private _width: number = 0;
- @observable private _height: number = 0;
+ @observable _width: number = 0;
+ @observable _height: number = 0;
- @observable private _mouseX: number = -1;
- @observable private _mouseY: number = -1;
- @observable private _shouldDisplay: boolean = false;
- private _ignoreUp = false;
- private _reactionDisposer?: IReactionDisposer;
+ @observable _mouseX: number = -1;
+ @observable _mouseY: number = -1;
+ @observable _shouldDisplay: boolean = false;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
-
+ makeObservable(this);
ContextMenu.Instance = this;
}
@@ -74,36 +77,27 @@ export class ContextMenu extends React.Component {
this._reactionDisposer?.();
}
- @action
- componentDidMount = () => {
+ componentDidMount() {
document.addEventListener('pointerdown', this.onPointerDown, true);
document.addEventListener('pointerup', this.onPointerUp);
- };
+ }
@action
clearItems() {
- this._items = [];
+ this._items.length = 0;
this._defaultPrefix = '';
this._defaultItem = undefined;
}
- _defaultPrefix: string = '';
- _defaultItem: ((name: string) => void) | undefined;
-
- findByDescription = (target: string, toLowerCase = false) => {
- return this._items.find(menuItem => {
- let reference = menuItem.description;
- toLowerCase && (reference = reference.toLowerCase());
- return reference === target;
- });
- };
+ findByDescription = (target: string, toLowerCase = false) =>
+ this._items.find(menuItem =>
+ (toLowerCase ? menuItem.description.toLowerCase() : menuItem.description) === target); // prettier-ignore
@action
addItem(item: ContextMenuProps) {
- if (this._items.indexOf(item) === -1) {
- this._items.push(item);
- }
+ !this._items.includes(item) && this._items.push(item);
}
+
@action
moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
const curInd = this._items.findIndex(i => i.description === item.description);
@@ -111,6 +105,7 @@ export class ContextMenu extends React.Component {
const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
this._items.splice(afterInd, 0, item);
}
+
@action
setDefaultItem(prefix: string, item: (name: string) => void) {
this._defaultPrefix = prefix;
@@ -126,7 +121,6 @@ export class ContextMenu extends React.Component {
return this._pageY + this._height > window.innerHeight - ContextMenu.buffer ? window.innerHeight - ContextMenu.buffer - this._height : Math.max(0, this._pageY);
}
- _onDisplay?: () => void = undefined;
@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
@@ -201,7 +195,7 @@ export class ContextMenu extends React.Component {
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
) : (
- <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+ <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this._selectedIndex} />
)
);
}
@@ -211,7 +205,7 @@ export class ContextMenu extends React.Component {
}
render() {
- return !this._display ? null : (
+ return (
<div
className="contextMenu-cont"
ref={action((r: any) => {
@@ -221,6 +215,7 @@ export class ContextMenu extends React.Component {
}
})}
style={{
+ display: this._display ? '' : 'none',
left: this.pageX,
...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
background: SettingsManager.userBackgroundColor,
@@ -242,17 +237,17 @@ export class ContextMenu extends React.Component {
@action
onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
- if (this.selectedIndex < this.flatItems.length - 1) {
- this.selectedIndex++;
+ if (this._selectedIndex < this.flatItems.length - 1) {
+ this._selectedIndex++;
}
e.preventDefault();
} else if (e.key === 'ArrowUp') {
- if (this.selectedIndex > 0) {
- this.selectedIndex--;
+ if (this._selectedIndex > 0) {
+ this._selectedIndex--;
}
e.preventDefault();
} else if (e.key === 'Enter' || e.key === 'Tab') {
- const item = this.flatItems[this.selectedIndex];
+ const item = this.flatItems[this._selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
} else if (this._searchString.startsWith(this._defaultPrefix)) {
@@ -268,12 +263,12 @@ export class ContextMenu extends React.Component {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
if (!this._searchString) {
- this.selectedIndex = -1;
+ this._selectedIndex = -1;
} else {
- if (this.selectedIndex === -1) {
- this.selectedIndex = 0;
+ 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 c2cbca3e1..3c9d821a9 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,12 +1,11 @@
-import React = require('react');
-import { observable, action, runInAction } from 'mobx';
+import * as React from 'react';
+import { observable, action, runInAction, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { UndoManager } from '../util/UndoManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
import { SettingsManager } from '../util/SettingsManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
export interface OriginalMenuProps {
description: string;
@@ -28,13 +27,17 @@ export interface SubmenuProps {
export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
@observer
-export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> {
+export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & { selected?: boolean }> {
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
- @action
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
- 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)));
}
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
index 6be2133ef..90f64b393 100644
--- a/src/client/views/DashboardView.scss
+++ b/src/client/views/DashboardView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables';
+@import './global/globalCssVariables.module';
.dashboard-view {
padding: 50px;
@@ -7,18 +7,18 @@
width: 100%;
position: absolute;
height: 100%;
- width:100%;
+ width: 100%;
padding-right: 0px;
overflow: auto;
- .left-menu {
- display: flex;
- justify-content: flex-start;
- flex-direction: column;
- width: 250px;
- min-width: 250px;
- gap: 5px;
- }
+ .left-menu {
+ display: flex;
+ justify-content: flex-start;
+ flex-direction: column;
+ width: 250px;
+ min-width: 250px;
+ gap: 5px;
+ }
.all-dashboards {
display: flex;
@@ -75,20 +75,20 @@
left: 0;
top: 0;
z-index: -1;
- }
+ }
}
.dashboard-container {
- border-radius: 10px;
- position: relative;
- cursor: pointer;
- width: 250px;
- height: 200px;
- outline: solid 2px $light-gray;
- display: flex;
- flex-direction: column;
- margin: 0 0px 30px 30px;
- overflow: hidden;
+ border-radius: 10px;
+ position: relative;
+ cursor: pointer;
+ width: 250px;
+ height: 200px;
+ outline: solid 2px $light-gray;
+ display: flex;
+ flex-direction: column;
+ margin: 0 0px 30px 30px;
+ overflow: hidden;
&:hover {
outline: solid 2px $light-blue;
@@ -122,18 +122,18 @@
background: 'lightgreen';
}
- .more {
- z-index: 100;
- }
+ .more {
+ z-index: 100;
+ }
- .background {
+ .background {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: -1;
- }
+ }
}
.new-dashboard {
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 9e2ed7822..523721b84 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaPlus } from 'react-icons/fa';
@@ -27,6 +27,7 @@ import './DashboardView.scss';
import { Colors } from './global/globalEnums';
import { MainViewModal } from './MainViewModal';
import { ButtonType } from './nodes/FontIconBox/FontIconBox';
+import { ObservableReactComponent } from './ObservableReactComponent';
enum DashboardGroup {
MyDashboards,
@@ -36,8 +37,12 @@ enum DashboardGroup {
// DashboardView is the view with the dashboard previews, rendered when the app first loads
@observer
-export class DashboardView extends React.Component {
+export class DashboardView extends ObservableReactComponent<{}> {
public static _urlState: HistoryUtil.DocUrl;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable private openModal = false;
@observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
@@ -370,7 +375,7 @@ export class DashboardView extends React.Component {
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
dashboardDoc['pane-count'] = 1;
freeformDoc.embedContainer = dashboardDoc;
dashboardDoc.myOverlayDocs = new List<Doc>();
@@ -379,13 +384,42 @@ export class DashboardView extends React.Component {
Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
DashboardView.SetupDashboardTrails(dashboardDoc);
-
+ DashboardView.SetupDashboardCalendars(dashboardDoc);
// open this new dashboard
Doc.ActiveDashboard = dashboardDoc;
Doc.ActivePage = 'dashboard';
Doc.ActivePresentation = undefined;
};
+ public static SetupDashboardCalendars(dashboardDoc: Doc){
+ // this section is creating the button document itself === myTrails = new Button
+
+ // create a a list of calendars (as a CalendarCollectionDocument) and store it on the new dashboard
+ const reqdOpts: DocumentOptions = {
+ title: 'My Calendars',
+ _layout_showTitle: 'title',
+ _height: 100,
+ treeView_HideTitle: true,
+ _layout_fitWidth: true,
+ _gridGap: 5,
+ _forceActive: true,
+ childDragAction: 'embed',
+ treeView_TruncateTitleWidth: 150,
+ ignoreClick: true,
+ contextMenuIcons: new List<string>(['plus']),
+ contextMenuLabels: new List<string>(['Create New Calendar']),
+ _lockedPosition: true,
+ layout_boxShadow: '0 0',
+ childDontRegisterViews: true,
+ dropAction: 'same',
+ isSystem: true,
+ layout_explainer: 'All of the calendars that you have created will appear here.',
+ };
+ const myCalendars = DocUtils.AssignScripts(Docs.Create.CalendarCollectionDocument([], reqdOpts));
+ // { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' }
+ dashboardDoc.myCalendars = new PrefetchProxy(myCalendars);
+ }
+
public static SetupDashboardTrails(dashboardDoc: Doc) {
// this section is creating the button document itself === myTrails = new Button
const reqdBtnOpts: DocumentOptions = {
@@ -429,7 +463,7 @@ export class DashboardView extends React.Component {
isSystem: true,
layout_explainer: 'All of the trails that you have created will appear here.',
};
- const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' });
+ const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.Document)' });
dashboardDoc.myTrails = new PrefetchProxy(myTrails);
const contextMenuScripts = [reqdBtnScript.onClick];
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 0bdcdc303..e098bc361 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -1,4 +1,4 @@
-import { computed, observable, runInAction } from 'mobx';
+import { computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DictationManager } from '../util/DictationManager';
@@ -19,6 +19,7 @@ export class DictationOverlay extends React.Component {
constructor(props: any) {
super(props);
+ makeObservable(this);
DictationOverlay.Instance = this;
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9ff27d9a0..235b0dc68 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,44 +1,42 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
+import * as React from 'react';
+import { returnFalse } from '../../Utils';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
-import { Cast } from '../../fields/Types';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
-import { returnFalse } from '../../Utils';
-import { DocUtils } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
+import { DocUtils } from '../documents/Documents';
+import { DocumentManager } from '../util/DocumentManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentView } from './nodes/DocumentView';
-import * as React from 'react';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
export interface DocComponentProps {
Document: Doc;
- fieldKey?: string;
LayoutTemplate?: () => Opt<Doc>;
LayoutTemplateString?: string;
}
export function DocComponent<P extends DocComponentProps>() {
- class Component extends React.Component<React.PropsWithChildren<P>> {
- //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- @computed get Document() {
- return this.props.Document;
+ class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
}
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() {
- return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+
+ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+ get Document() {
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() {
- return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.());
+ return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() {
- return this.props.Document[DocData] as Doc;
- }
- // key where data is stored
- @computed get fieldKey() {
- return this.props.fieldKey;
+ return this._props.Document[DocData] as Doc;
}
}
return Component;
@@ -47,34 +45,31 @@ export function DocComponent<P extends DocComponentProps>() {
/// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox)
interface ViewBoxBaseProps {
Document: Doc;
- DataDoc?: Doc;
+ TemplateDataDocument?: Doc;
DocumentView?: () => DocumentView;
fieldKey: string;
isSelected: () => boolean;
isContentActive: () => boolean | undefined;
renderDepth: number;
- rootSelected: () => boolean;
}
export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
- class Component extends React.Component<React.PropsWithChildren<P>> {
+ class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
//@computed get Document(): T { return schemaCtor(this.props.Document); }
-
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() {
- return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+ get Document() {
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() {
- return Doc.Layout(this.props.Document);
+ return Doc.Layout(this.Document);
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() {
- return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
}
// key where data is stored
- @computed get fieldKey() {
- return this.props.fieldKey;
+ get fieldKey() {
+ return this._props.fieldKey;
}
}
return Component;
@@ -83,41 +78,41 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
/// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
export interface ViewBoxAnnotatableProps {
Document: Doc;
- DataDoc?: Doc;
+ TemplateDataDocument?: Doc;
fieldKey: string;
filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
isContentActive: () => boolean | undefined;
select: (isCtrlPressed: boolean) => void;
whenChildContentsActiveChanged: (isActive: boolean) => void;
isSelected: () => boolean;
- rootSelected: () => boolean;
renderDepth: number;
isAnnotationOverlay?: boolean;
}
export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
- class Component extends React.Component<React.PropsWithChildren<P>> {
+ class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable _annotationKeySuffix = () => 'annotations';
@observable _isAnyChildContentActive = false;
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document() {
- return this.props.Document;
- }
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() {
- return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() {
- return Doc.Layout(this.props.Document);
+ return Doc.Layout(this.Document);
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() {
- return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
}
// key where data is stored
@computed get fieldKey() {
- return this.props.fieldKey;
+ return this._props.fieldKey;
}
isAnyChildContentActive = () => this._isAnyChildContentActive;
@@ -138,7 +133,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = this.rootDoc !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
+ const recent = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
toRemove.forEach(doc => {
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
@@ -148,7 +143,11 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
}
});
- this.isAnyChildContentActive() && this.props.select(false);
+ if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
+ (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
+ } else {
+ this.isAnyChildContentActive() && this._props.select(false);
+ }
return true;
}
@@ -160,7 +159,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
// moving it into the target.
@action.bound
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
- if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+ if (Doc.AreProtosEqual(this._props.Document, targetCollection)) {
return true;
}
const first = doc instanceof Doc ? doc : doc[0];
@@ -172,10 +171,10 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
@action.bound
addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
+ if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) {
return false;
}
- const targetDataDoc = this.rootDoc[DocData];
+ const targetDataDoc = this.dataDoc;
const effectiveAcl = GetEffectiveAcl(targetDataDoc);
if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
@@ -186,9 +185,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
added.forEach(doc => {
doc._dragOnlyWithinContainer = undefined;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
+ if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.Document;
else Doc.GetProto(doc).annotationOn = undefined;
- Doc.SetContainer(doc, this.rootDoc);
+ Doc.SetContainer(doc, this.Document);
inheritParentAcls(targetDataDoc, doc, true);
});
@@ -201,7 +200,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return true;
};
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
}
return Component;
}
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 1abf33cac..11614d627 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
$linkGap: 3px;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c1ec5b4a4..f0887676f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,36 +1,29 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, runInAction } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
-import { Cast, DocCast, NumCast } from '../../fields/Types';
+import * as React from 'react';
import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Doc } from '../../fields/Doc';
+import { Cast, DocCast } from '../../fields/Types';
+import { DocUtils } from '../documents/Documents';
+import { CalendarManager } from '../util/CalendarManager';
import { DragManager } from '../util/DragManager';
import { IsFollowLinkScript } from '../util/LinkFollower';
import { SelectionManager } from '../util/SelectionManager';
import { SharingManager } from '../util/SharingManager';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { TabDocView } from './collections/TabDocView';
+import { UndoManager, undoBatch } from '../util/UndoManager';
import './DocumentButtonBar.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { TabDocView } from './collections/TabDocView';
import { Colors } from './global/globalEnums';
import { LinkPopup } from './linking/LinkPopup';
-import { MetadataEntryMenu } from './MetadataEntryMenu';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView';
import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
import { PinProps } from './nodes/trails';
-import { TemplateMenu } from './TemplateMenu';
-import React = require('react');
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
+import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
const cloud: IconProp = 'cloud-upload-alt';
const fetch: IconProp = 'sync-alt';
@@ -42,12 +35,11 @@ enum UtilityButtonState {
}
@observer
-export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[]; stack?: any }, {}> {
+export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
private _dragRef = React.createRef<HTMLDivElement>();
private _pullAnimating = false;
private _pushAnimating = false;
private _pullColorAnimating = false;
-
@observable private pushIcon: IconProp = 'arrow-alt-circle-up';
@observable private pullIcon: IconProp = 'arrow-alt-circle-down';
@observable private pullColor: string = 'white';
@@ -60,9 +52,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
public static hasPushedHack = false;
public static hasPulledHack = false;
- constructor(props: { views: () => (DocumentView | undefined)[] }) {
+ constructor(props: any) {
super(props);
- runInAction(() => (DocumentButtonBar.Instance = this));
+ makeObservable(this);
+ DocumentButtonBar.Instance = this;
}
public startPullOutcome = action((success: boolean) => {
@@ -111,110 +104,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
});
get view0() {
- return this.props.views()?.[0];
- }
-
- @computed
- get considerGoogleDocsPush() {
- const targetDoc = this.view0?.props.Document;
- const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
- const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
- return !targetDoc ? null : (
- <Tooltip
- title={
- <>
- <div className="dash-tooltip">{`${published ? 'Push' : 'Publish'} to Google Docs`}</div>
- </>
- }>
- <div
- className="documentButtonBar-button"
- style={{ animation }}
- onClick={async () => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- !published && runInAction(() => (this.isAnimatingPulse = true));
- DocumentButtonBar.hasPushedHack = false;
- targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? 'sm' : 'xs'} />
- </div>
- </Tooltip>
- );
+ return this._props.views()?.[0];
}
- @computed
- get considerGoogleDocsPull() {
- const targetDoc = this.view0?.props.Document;
- const dataDoc = targetDoc && Doc.GetProto(targetDoc);
- const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
-
- const title = (() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default:
- return `${!dataDoc?.googleDocUnchanged ? 'Pull from' : 'Fetch'} Google Docs`;
- case UtilityButtonState.OpenRight:
- return 'Open in Right Split';
- case UtilityButtonState.OpenExternally:
- return 'Open in new Browser Tab';
- }
- })();
-
- return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : (
- <Tooltip title={<div className="dash-tooltip">{title}</div>}>
- <div
- className="documentButtonBar-button"
- style={{ backgroundColor: this.pullColor }}
- onPointerEnter={action(e => {
- if (e.altKey) {
- this.openHover = UtilityButtonState.OpenExternally;
- } else if (e.shiftKey) {
- this.openHover = UtilityButtonState.OpenRight;
- }
- })}
- onPointerLeave={action(() => (this.openHover = UtilityButtonState.Default))}
- onClick={async e => {
- const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
- if (e.shiftKey) {
- e.preventDefault();
- let googleDoc = await Cast(dataDoc.googleDoc, Doc);
- if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, data_useCors: false };
- googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
- dataDoc.googleDoc = googleDoc;
- }
- CollectionDockingView.AddSplit(googleDoc, OpenWhereMod.right);
- } else if (e.altKey) {
- e.preventDefault();
- window.open(googleDocUrl);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
- dataDoc.googleDocUnchanged && runInAction(() => (this.isAnimatingFetch = true));
- }
- }}>
- <FontAwesomeIcon
- className="documentdecorations-icon"
- size="sm"
- style={{ WebkitAnimation: animation, MozAnimation: animation }}
- icon={(() => {
- // prettier-ignore
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
- case UtilityButtonState.OpenRight: return 'arrow-alt-circle-right';
- case UtilityButtonState.OpenExternally: return 'share';
- }
- })()}
- />
- </div>
- </Tooltip>
- );
- }
@observable subFollow = '';
@computed
get followLinkButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => {
const tooltip = `Follow ${this.subPin}documents`;
return !tooltip ? null : (
@@ -229,7 +125,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
onPointerLeave={action(e => (this.subPin = ''))}
onClick={e => {
- this.props.views().forEach(dv => click(dv!.rootDoc));
+ this._props.views().forEach(dv => click(dv!.Document));
e.stopPropagation();
}}
/>
@@ -243,7 +139,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<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?.docView?.toggleFollowLink(undefined, false)))}>
+ onClick={undoBatch(e => this._props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
<div className="documentButtonBar-followTypes">
{followBtn(
true,
@@ -260,7 +156,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@observable subLink = '';
@computed get linkButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc || !this.view0 ? null : (
<div className="documentButtonBar-icon documentButtonBar-link">
<div className="documentButtonBar-linkTypes">
@@ -304,7 +200,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onPointerLeave={action(e => (this.subEndLink = ''))}
onClick={e => {
this.view0 &&
- DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, {
+ DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
pinDocLayout: pinLayout,
pinData: !pinContent ? {} : { poslayoutview: true, dataannos: true, dataview: pinContent },
} as PinProps);
@@ -331,7 +227,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@observable subPin = '';
@computed
get pinButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const pinBtn = (pinLayoutView: boolean, pinContentView: boolean, icon: IconProp) => {
const tooltip = `Pin Document and Save ${this.subPin} to trail`;
return !tooltip ? null : (
@@ -353,10 +249,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
)}
onPointerLeave={action(e => (this.subPin = ''))}
onClick={e => {
- const docs = this.props
+ const docs = this._props
.views()
.filter(v => v)
- .map(dv => dv!.rootDoc);
+ .map(dv => dv!.Document);
TabDocView.PinDoc(docs, {
pinAudioPlay: true,
pinDocLayout: pinLayoutView,
@@ -372,14 +268,14 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
);
};
return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
+ <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views.length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
<div
className="documentButtonBar-icon documentButtonBar-pin"
onClick={e => {
- const docs = this.props
+ const docs = this._props
.views()
.filter(v => v)
- .map(dv => dv!.rootDoc);
+ .map(dv => dv!.Document);
TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
e.stopPropagation();
}}>
@@ -396,7 +292,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get shareButton() {
- const targetDoc = this.view0?.props.Document;
+ 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)}>
@@ -408,36 +304,29 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get menuButton() {
- const targetDoc = this.view0?.props.Document;
+ 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' }} onClick={this.openContextMenu}>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
<FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
</div>
</Tooltip>
);
}
+
@computed
- get metadataButton() {
- const view0 = this.view0;
- return !view0 ? null : (
- <Tooltip title={<div className="dash-tooltip">Show metadata panel</div>}>
- <div className="documentButtonBar-linkFlyout">
- <Flyout
- anchorPoint={anchorPoints.LEFT_TOP}
- content={
- <MetadataEntryMenu
- docs={this.props
- .views()
- .filter(dv => dv)
- .map(dv => dv!.props.Document)}
- suggestWithFunction
- /> /* tfs: @bcz This might need to be the data document? */
- }>
- <div className={'documentButtonBar-linkButton-' + 'empty'} onPointerDown={e => e.stopPropagation()}>
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
- </div>
- </Flyout>
+ get calendarButton() {
+ const targetDoc = this.view0?.Document;
+ return !targetDoc ? null : (
+ <Tooltip title={<div className="dash-calendar-button">Open calendar menu</div>}>
+ <div
+ className="documentButtonBar-icon"
+ style={{ color: 'white' }}
+ onClick={e => {
+ console.log('hi: ', CalendarManager.Instance);
+ CalendarManager.Instance.open(this.view0, targetDoc);
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup} />
</div>
</Tooltip>
);
@@ -447,7 +336,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
_stopFunc: () => void = emptyFunction;
@computed
get recordButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
<Tooltip title={<div className="dash-tooltip">Press to record audio annotation</div>}>
<div
@@ -455,7 +344,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
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,
@@ -482,8 +371,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onEmbedButtonMoved = () => {
if (this._dragRef.current) {
const dragDocView = this.view0!;
- const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
+ const [left, top] = dragDocView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
dragData.defaultDropAction = 'embed';
dragData.canEmbed = true;
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
@@ -497,11 +386,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get templateButton() {
const view0 = this.view0;
- const views = this.props.views();
+ const views = this._props.views();
return !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))}>
- <Flyout
+ {/* <Flyout
anchorPoint={anchorPoints.LEFT_TOP}
onOpen={action(() => (this._embedDown = true))}
onClose={action(() => (this._embedDown = false))}
@@ -516,14 +405,14 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />
</div>
- </Flyout>
+ </Flyout> */}
</div>
</Tooltip>
);
}
- openContextMenu = (e: React.MouseEvent) => {
- let child = SelectionManager.Views()[0].ContentDiv!.children[0];
+ openContextMenu = (e: PointerEvent) => {
+ let child = SelectionManager.Views[0].ContentDiv!.children[0];
while (child.children.length) {
const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
@@ -559,27 +448,24 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@action
toggleTrail = (e: React.PointerEvent) => {
- const rootView = this.props.views()[0];
- const rootDoc = rootView?.rootDoc;
- if (rootDoc) {
- const anchor = rootView.ComponentView?.getAnchor?.(true) ?? rootDoc;
+ const rootView = this._props.views()[0];
+ const doc = rootView?.Document;
+ if (doc) {
+ const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
if (trail !== anchor.presentationTrail) {
DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
anchor.presentationTrail = trail;
}
Doc.ActivePresentation = trail;
- this.props.views().lastElement()?.props.addDocTab(trail, OpenWhere.replaceRight);
+ this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
}
e.stopPropagation();
};
render() {
- if (!this.view0) return null;
+ const doc = this.view0?.Document;
+ if (!doc || !this.view0) return null;
- const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
- const doc = this.view0?.props.Document;
- const considerPull = isText && this.considerGoogleDocsPull;
- const considerPush = isText && this.considerGoogleDocsPush;
return (
<div className="documentButtonBar">
<div className="documentButtonBar-button">
@@ -589,9 +475,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div style={{ position: 'absolute', zIndex: 1000 }}>
<LinkPopup
key="popup"
- linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))}
- linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
- linkFrom={() => this.props.views().lastElement()?.rootDoc}
+ 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}
/>
</div>
) : (
@@ -599,22 +485,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
)}
{DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
- {
- Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
- /*<div className="documentButtonBar-button"> {this.metadataButton} </div> */
- }
- {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+ {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>}
+ {!SelectionManager.Views?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
<div className="documentButtonBar-button">{this.pinButton}</div>
<div className="documentButtonBar-button">{this.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" style={{ display: !considerPush ? 'none' : '' }}>
- {this.considerGoogleDocsPush}
- </div>
- )}
- <div className="documentButtonBar-button" style={{ display: !considerPull ? 'none' : '' }}>
- {this.considerGoogleDocsPull}
- </div>
<div className="documentButtonBar-button">{this.menuButton}</div>
</div>
);
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index bbd951481..ac0ef054c 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
$linkGap: 3px;
$headerHeight: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d4b474de9..7003485d2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,12 +1,12 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { Tooltip } from '@mui/material';
import { IconButton } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { FaUndo } from 'react-icons/fa';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit } from '../../fields/DocSymbols';
import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
import { ScriptField } from '../../fields/ScriptField';
@@ -33,11 +33,17 @@ import { LightboxView } from './LightboxView';
import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
-import React = require('react');
-import _ = require('lodash');
-
+import * as React from 'react';
+import { ObservableReactComponent } from './ObservableReactComponent';
+
+interface DocumentDecorationsProps {
+ PanelWidth: number;
+ PanelHeight: number;
+ boundsLeft: number;
+ boundsTop: number;
+}
@observer
-export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
+export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> {
static Instance: DocumentDecorations;
private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
@@ -61,27 +67,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _showRotCenter = false; // whether to show a draggable green dot that represents the center of rotation
@observable private _rotCenter = [0, 0]; // the center of rotation in object coordinates (0,0) = object center (not top left!)
- constructor(props: any) {
+ constructor(props: React.PropsWithChildren<DocumentDecorationsProps>) {
super(props);
+ makeObservable(this);
+
DocumentDecorations.Instance = this;
document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
action(e => {
const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
const {x,y} = Utils.rotPt(e.clientX - center.x,
e.clientY - center.y,
- NumCast(SelectionManager.Views().lastElement()?.screenToLocalTransform().Rotate));
+ NumCast(SelectionManager.Views.lastElement()?.screenToLocalTransform().Rotate));
(this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
- (this.Bounds.x > center.x+x + this._resizeBorderWidth / 2 ||
- this.Bounds.r < center.x+x - this._resizeBorderWidth / 2 ||
- this.Bounds.y > center.y+y + this._resizeBorderWidth / 2 ||
- this.Bounds.b < center.y+y - this._resizeBorderWidth / 2)));
+ (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x ||
+ this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
})); // prettier-ignore
}
@computed get ClippedBounds() {
const bounds = this.Bounds;
- const leftBounds = this.props.boundsLeft;
- const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
+ const leftBounds = this._props.boundsLeft;
+ const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop;
bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
const borderRadiusDraggerWidth = 15;
@@ -93,15 +99,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@computed get Bounds() {
return (LinkFollower.IsFollowing || DocumentView.ExploreMode) ?
{ x: 0, y: 0, r: 0, b: 0 }
- : SelectionManager.Views()
- .filter(dv => dv.props.renderDepth > 0)
+ : SelectionManager.Views
+ .filter(dv => dv._props.renderDepth > 0)
.map(dv => dv.getBounds())
.reduce((bounds, rect) => !rect ? bounds
: { x: Math.min(rect.left, bounds.x),
y: Math.min(rect.top, bounds.y),
r: Math.max(rect.right, bounds.r),
b: Math.max(rect.bottom, bounds.b),
- c: SelectionManager.Views().length === 1 ? rect.center : undefined },
+ c: SelectionManager.Views.length === 1 ? rect.center : undefined },
{ x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }); // prettier-ignore
}
@@ -115,14 +121,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
UndoManager.RunInBatch(
() =>
titleFieldKey &&
- SelectionManager.Views().forEach(d => {
+ SelectionManager.Views.forEach(d => {
if (titleFieldKey === 'title') {
d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
- if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
- Doc.RemFromMyPublished(d.rootDoc);
+ if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
+ Doc.RemFromMyPublished(d.Document);
}
- if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
- Doc.AddToMyPublished(d.rootDoc);
+ if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
+ Doc.AddToMyPublished(d.Document);
}
}
//@ts-ignore
@@ -130,20 +136,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (titleField.toString().startsWith('<this>')) {
const title = titleField.toString().replace(/<this>\.?/, '');
- const curKey = Doc.LayoutFieldKey(d.rootDoc);
+ const curKey = Doc.LayoutFieldKey(d.Document);
if (curKey !== title) {
if (title) {
if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
- d.rootDoc.layout_fieldKey = `layout_${title}`;
- d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title);
- d.rootDoc[`${title}_nativeWidth`] = d.rootDoc[`${title}_nativeHeight`] = 0;
+ d.Document.layout_fieldKey = `layout_${title}`;
+ d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
+ d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
}
} else {
- d.rootDoc.layout_fieldKey = undefined;
+ d.Document.layout_fieldKey = undefined;
}
}
} else {
- Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+ Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
}
}),
'edit title'
@@ -159,7 +165,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
onContainerDown = (e: React.PointerEvent) => {
- const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
+ const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
e.stopPropagation();
@@ -167,7 +173,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
onTitleDown = (e: React.PointerEvent) => {
- const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
+ const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
setupMoveUpEvents(
this,
@@ -190,27 +196,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
- const dragDocView = SelectionManager.Views()[0];
- const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.rootDoc);
+ const dragDocView = SelectionManager.Views[0];
+ const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
return false;
}
const containers = new Set<Doc | undefined>();
- SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
+ SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer)));
if (containers.size > 1) return false;
const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
const dragData = new DragManager.DocumentDragData(
- SelectionManager.Views().map(dv => dv.props.Document),
- dragDocView.props.dropAction
+ SelectionManager.Views.map(dv => dv.Document),
+ dragDocView._props.dropAction
);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
- dragData.moveDocument = dragDocView.props.moveDocument;
- dragData.removeDocument = dragDocView.props.removeDocument;
+ dragData.offset = dragDocView._props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+ dragData.moveDocument = dragDocView._props.moveDocument;
+ dragData.removeDocument = dragDocView._props.removeDocument;
dragData.isDocDecorationMove = true;
dragData.canEmbed = dragTitle;
this._hidden = true;
DragManager.StartDocumentDrag(
- SelectionManager.Views().map(dv => dv.ContentDiv!),
+ SelectionManager.Views.map(dv => dv.ContentDiv!),
dragData,
e.x,
e.y,
@@ -225,7 +231,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
_deleteAfterIconify = false;
_iconifyBatch: UndoManager.Batch | undefined;
onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
- const views = SelectionManager.Views().filter(v => v && v.props.renderDepth > 0);
+ 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;
@@ -233,11 +239,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
if (this._deleteAfterIconify) {
views.forEach(iconView => {
- Doc.setNativeView(iconView.props.Document);
- if (iconView.props.Document.activeFrame) {
- iconView.props.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.
+ 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.
} else {
- iconView.props.removeDocument?.(iconView.props.Document);
+ iconView._props.removeDocument?.(iconView.Document);
}
});
views.forEach(SelectionManager.DeselectView);
@@ -258,27 +264,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
onMaximizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views().lastElement().rootDoc]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
+ setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views.lastElement().Document]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
e.stopPropagation();
};
onMaximizeClick = (e: any): void => {
- const selectedDocs = SelectionManager.Views();
+ const selectedDocs = SelectionManager.Views;
if (selectedDocs.length) {
if (e.ctrlKey) {
// open an embedding in a new tab with Ctrl Key
- CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].rootDoc), OpenWhereMod.right);
+ CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].Document), OpenWhereMod.right);
} else if (e.shiftKey) {
// open centered in a new workspace with Shift Key
- const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
+ const embedding = Doc.MakeEmbedding(selectedDocs[0].Document);
embedding.embedContainer = undefined;
embedding.x = -NumCast(embedding._width) / 2;
embedding.y = -NumCast(embedding._height) / 2;
CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
} else if (e.altKey) {
// open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].rootDoc, OpenWhereMod.right);
+ CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right);
} else {
- var openDoc = selectedDocs[0].rootDoc;
+ var openDoc = selectedDocs[0].Document;
if (openDoc.layout_fieldKey === 'layout_icon') {
openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
Doc.deiconifyView(openDoc);
@@ -286,7 +292,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
LightboxView.Instance.SetLightboxDoc(
openDoc,
undefined,
- selectedDocs.slice(1).map(view => view.rootDoc)
+ selectedDocs.slice(1).map(view => view.Document)
);
}
}
@@ -294,11 +300,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
onIconifyClick = (): void => {
- SelectionManager.Views().forEach(dv => dv?.iconify());
+ SelectionManager.Views.forEach(dv => dv?.iconify());
SelectionManager.DeselectAll();
};
- onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
+ onSelectContainerDocClick = () => SelectionManager.Views?.[0]?._props.docViewPath?.().lastElement()?.select(false);
/**
* sets up events when user clicks on the border radius editor
*/
@@ -313,7 +319,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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 => {
+ SelectionManager.Docs.map(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`;
@@ -338,7 +344,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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 =>
+ () => SelectionManager.Docs.forEach(doc =>
doc._pointerEvents = (doc._lockedPosition = !doc._lockedPosition)? 'none' : undefined ),
'toggleBackground' ) // prettier-ignore
);
@@ -346,24 +352,24 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
- const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+ const newloccentern = seldocview._props.ScreenToLocalTransform().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.rootDoc._rotation) / 180) * Math.PI);
- seldocview.rootDoc.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
- seldocview.rootDoc.rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+ 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);
};
@action
onRotateCenterDown = (e: React.PointerEvent): void => {
this._isRotating = true;
- const seldocview = SelectionManager.Views()[0];
+ const seldocview = SelectionManager.Views[0];
setupMoveUpEvents(
this,
e,
(e: 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.rootDoc.rotation_centerX = seldocview.rootDoc.rotation_centerY = 0))
+ action(e => (seldocview.Document.rotation_centerX = seldocview.Document.rotation_centerY = 0))
); // prettier-ignore
};
@@ -372,27 +378,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
this._isRotating = true;
const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] };
const rotateUndo = UndoManager.StartBatch('drag rotation');
- const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
+ const selectedInk = SelectionManager.Views.filter(i => i.ComponentView instanceof InkingStroke);
const centerPoint = this.rotCenter.slice();
const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
- const seldocview = SelectionManager.Views()[0];
- SelectionManager.Views().forEach(dv => {
- const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI;
- const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
- const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2];
+ const seldocview = SelectionManager.Views[0];
+ SelectionManager.Views.forEach(dv => {
+ const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
+ const localRotCtr = dv._props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+ const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
- const unrotatedDocPos = { x: NumCast(dv.rootDoc.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.rootDoc.y) + localRotCtrOffset[1] - startRotCtr.y };
- infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot });
+ const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
+ infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
});
const infoRot = (angle: number, isAbs = false) => {
- SelectionManager.Views().forEach(
+ SelectionManager.Views.forEach(
action(dv => {
- const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.rootDoc)!;
+ const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.Document)!;
const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle);
- infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
- dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
- dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
- dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
+ infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
+ dv.Document.x = infos.get(dv.Document)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
+ dv.Document.y = infos.get(dv.Document)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
+ dv.Document._rotation = ((isAbs ? 0 : NumCast(dv.Document._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
})
);
};
@@ -412,7 +418,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return false;
}, // moveEvent
action(() => {
- const oldRotation = NumCast(seldocview.rootDoc._rotation);
+ const oldRotation = NumCast(seldocview.Document._rotation);
const diff = oldRotation - Math.round(oldRotation / 45) * 45;
if (Math.abs(diff) < 5) {
if (selectedInk.length) {
@@ -433,7 +439,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@action
onPointerDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs().lastElement());
+ SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
DocumentView.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
@@ -442,7 +448,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
this._offset = { x: this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX, y: this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY };
this._resizeUndo = UndoManager.StartBatch('drag resizing');
this._snapPt = { x: e.pageX, y: e.pageY };
- SelectionManager.Views().forEach(docView => docView.CollectionFreeFormView?.dragStarting(false, false));
+ SelectionManager.Views.forEach(docView => docView.CollectionFreeFormView?.dragStarting(false, false));
};
projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => {
@@ -456,12 +462,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
return [a[0] + atob[0] * t, a[1] + atob[1] * t];
};
- const tl = docView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const tl = docView._props.ScreenToLocalTransform().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 => {
- const first = SelectionManager.Views()[0];
- const effectiveAcl = GetEffectiveAcl(first.rootDoc);
+ const first = SelectionManager.Views[0];
+ const effectiveAcl = GetEffectiveAcl(first.Document);
if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
if (!first) return false;
var fixedAspect = Doc.NativeAspect(first.layoutDoc);
@@ -477,10 +483,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
!this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
this._interactionLock = true;
this._snapPt = thisPt;
- e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.props.Document) && docView.toggleNativeDimensions()));
- const fixedAspect = SelectionManager.Docs().some(this.hasFixedAspect);
+ 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};
- SelectionManager.Views().forEach(docView =>
+ 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)));
}); // prettier-ignore
@@ -516,7 +522,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
// resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
//
resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => {
- const doc = docView.rootDoc;
+ const doc = docView.Document;
if (doc.isGroup) {
DocListCast(doc.data)
.map(member => DocumentManager.Instance.getDocumentView(member, docView)!)
@@ -524,7 +530,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
doc.xPadding = NumCast(doc.xPadding) * scale.x;
doc.yPadding = NumCast(doc.yPadding) * scale.y;
} else {
- const refCent = docView.props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+ const refCent = docView._props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
@@ -551,8 +557,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (setData) Doc.SetNativeHeight(Doc.GetProto(doc), NumCast(doc._nativeHeight));
}
- doc._width = NumCast(doc._width) * scale.x;
- doc._height = NumCast(doc._height) * scale.y;
+ doc._width = Math.max(1, NumCast(doc._width) * scale.x);
+ doc._height = Math.max(1, NumCast(doc._height) * scale.y);
const { deltaX, deltaY } = this.realignRefPt(doc, refCent, initWidth, initHeight);
doc.x = NumCast(doc.x) + deltaX;
doc.y = NumCast(doc.y) + deltaY;
@@ -594,7 +600,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
this._resizeUndo?.end();
// detect layout_autoHeight gesture and apply
- SelectionManager.Docs().forEach(doc => NumCast(doc._height) < 20 && (doc._layout_autoHeight = true));
+ SelectionManager.Docs.forEach(doc => NumCast(doc._height) < 20 && (doc._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 || [] }))
@@ -611,26 +617,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@computed
get selectionTitle(): string {
- if (SelectionManager.Views().length === 1) {
- const selected = SelectionManager.Views()[0];
- if (selected.ComponentView?.getTitle?.()) {
- return selected.ComponentView.getTitle();
- }
+ if (SelectionManager.Views.length === 1) {
+ const selected = SelectionManager.Views[0];
if (this._titleControlString.startsWith('=')) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || '';
+ return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
}
if (this._titleControlString.startsWith('#')) {
- return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+ return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
}
return this._accumulatedTitle;
}
- return SelectionManager.Views().length > 1 ? '-multiple-' : '-unset-';
+ return SelectionManager.Views.length > 1 ? '-multiple-' : '-unset-';
}
@computed get rotCenter() {
- const lastView = SelectionManager.Views().lastElement();
+ const lastView = SelectionManager.Views.lastElement();
if (lastView) {
- const invXf = lastView.props.ScreenToLocalTransform().inverse();
+ const invXf = lastView._props.ScreenToLocalTransform().inverse();
const seldoc = lastView.layoutDoc;
const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
@@ -640,31 +643,31 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
render() {
const { b, r, x, y } = this.Bounds;
- const seldocview = SelectionManager.Views().lastElement();
- if (SnappingManager.GetIsDragging() || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
+ const seldocview = SelectionManager.Views.lastElement();
+ if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
setTimeout(action(() => (this._showNothing = true)));
return null;
}
// sharing
- const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc);
+ 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;
// hide the decorations if the parent chooses to hide it or if the document itself hides it
- const hideDecorations = SnappingManager.GetIsResizing() || seldocview.props.hideDecorations || seldocview.rootDoc.layout_hideDecorations;
+ const hideDecorations = SnappingManager.IsResizing || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
const hideResizers =
- ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
- const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
- const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
+ ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview._props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
+ const hideTitle = this._showNothing || hideDecorations || seldocview._props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = hideDecorations || seldocview._props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
const hideOpenButton =
this._showNothing ||
hideDecorations ||
- seldocview.props.hideOpenButton ||
- seldocview.rootDoc.layout_hideOpenButton ||
- SelectionManager.Views().some(docView => docView.rootDoc._dragOnlyWithinContainer || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
+ seldocview._props.hideOpenButton ||
+ seldocview.Document.layout_hideOpenButton ||
+ SelectionManager.Views.some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
this._isRounding ||
this._isRotating;
const hideDeleteButton =
@@ -672,11 +675,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
hideDecorations ||
this._isRounding ||
this._isRotating ||
- seldocview.props.hideDeleteButton ||
- seldocview.rootDoc.hideDeleteButton ||
- SelectionManager.Views().some(docView => {
- const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
- return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin;
+ seldocview._props.hideDeleteButton ||
+ seldocview.Document.hideDeleteButton ||
+ SelectionManager.Views.some(docView => {
+ const collectionAcl = docView._props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView._props.docViewPath().lastElement().dataDoc) : AclEdit;
+ return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
});
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
@@ -688,13 +691,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const bounds = this.ClippedBounds;
const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
- const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
- const rotation = SelectionManager.Views().length == 1 ? seldocview.screenToLocalTransform().inverse().RotateDeg : 0;
+ 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.screenToLocalTransform().inverse().RotateDeg : 0;
// Radius constants
const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
- const borderRadius = numberValue(Cast(seldocview.rootDoc.layout_borderRounding, 'string', null));
- const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.height) / 2);
+ const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null));
+ const docMax = Math.min(NumCast(seldocview.Document.width) / 2, NumCast(seldocview.Document.height) / 2);
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
@@ -742,7 +745,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
{sharingMenu}
{!useLock ? null : (
<Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
- <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
+ <div className="documentDecorations-lock" style={{ color: seldocview.Document._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
<FontAwesomeIcon size="sm" icon="lock" />
</div>
</Tooltip>
@@ -751,7 +754,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
const centery = hideTitle ? 0 : this._titleHeight;
const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
- const freeformDoc = SelectionManager.Views().some(v => v.CollectionFreeFormDocumentView);
+ const freeformDoc = SelectionManager.Views.some(v => v.CollectionFreeFormDocumentView);
return (
<div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
<div
@@ -762,9 +765,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
transformOrigin,
- background: SnappingManager.GetShiftKey() ? undefined : 'yellow',
- pointerEvents: SnappingManager.GetShiftKey() || DocumentView.Interacting ? 'none' : 'all',
- display: SelectionManager.Views().length <= 1 || hideDecorations ? 'none' : undefined,
+ background: SnappingManager.ShiftKey ? undefined : 'yellow',
+ pointerEvents: SnappingManager.ShiftKey || DocumentView.Interacting ? 'none' : 'all',
+ display: SelectionManager.Views.length <= 1 || hideDecorations ? 'none' : undefined,
transform: `rotate(${rotation}deg)`,
}}
onPointerDown={this.onBackgroundDown}
@@ -801,11 +804,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} />
<div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />
<div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
- {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement()
- ? null
- : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
</>
)}
+ {seldocview._props.renderDepth <= 1 || !seldocview._props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
{useRounding && (
<div
className="documentDecorations-borderRadius"
@@ -823,7 +824,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
style={{
transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
- <DocumentButtonBar views={SelectionManager.Views} />
+ <DocumentButtonBar views={() => SelectionManager.Views} />
</div>
)}
</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index abb7ed7ee..73ac1b032 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,10 +1,12 @@
-import React = require('react');
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import './EditableView.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { ObservableReactComponent } from './ObservableReactComponent';
import { OverlayView } from './OverlayView';
export interface EditableProps {
@@ -26,6 +28,7 @@ export interface EditableProps {
* The contents to render when not editing
*/
contents: any;
+ fieldContents?: FieldViewProps;
fontStyle?: string;
fontSize?: number;
height?: number | 'auto';
@@ -57,7 +60,7 @@ export interface EditableProps {
* of the content, and set the value based on the entered string.
*/
@observer
-export class EditableView extends React.Component<EditableProps> {
+export class EditableView extends ObservableReactComponent<EditableProps> {
private _ref = React.createRef<HTMLDivElement>();
private _inputref: HTMLInputElement | HTMLTextAreaElement | null = null;
_overlayDisposer?: () => void;
@@ -66,7 +69,8 @@ export class EditableView extends React.Component<EditableProps> {
constructor(props: EditableProps) {
super(props);
- this._editing = this.props.editing ? true : false;
+ makeObservable(this);
+ this._editing = this._props.editing ? true : false;
}
componentDidMount(): void {
@@ -89,12 +93,12 @@ export class EditableView extends React.Component<EditableProps> {
);
}
- @action
- componentDidUpdate() {
- if (this._editing && this.props.editing === false) {
+ componentDidUpdate(prevProps: Readonly<EditableProps>) {
+ super.componentDidUpdate(prevProps);
+ if (this._editing && this._props.editing === false) {
this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
- } else if (this.props.editing !== undefined) {
- this._editing = this.props.editing;
+ } else if (this._props.editing !== undefined) {
+ this._editing = this._props.editing;
}
}
@@ -120,28 +124,28 @@ export class EditableView extends React.Component<EditableProps> {
case 'Tab':
e.stopPropagation();
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
- this.props.OnTab?.(e.shiftKey);
+ this._props.OnTab?.(e.shiftKey);
break;
case 'Backspace':
e.stopPropagation();
- if (!e.currentTarget.value) this.props.OnEmpty?.();
+ if (!e.currentTarget.value) this._props.OnEmpty?.();
break;
case 'Enter':
- if (this.props.allowCRs !== true) {
+ if (this._props.allowCRs !== true) {
e.stopPropagation();
if (!e.ctrlKey) {
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
- } else if (this.props.OnFillDown) {
- this.props.OnFillDown(e.currentTarget.value);
+ } else if (this._props.OnFillDown) {
+ this._props.OnFillDown(e.currentTarget.value);
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
}
}
break;
case 'Escape':
e.stopPropagation();
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
break;
case 'ArrowUp':
case 'ArrowDown':
@@ -155,30 +159,30 @@ export class EditableView extends React.Component<EditableProps> {
case 'Control':
break;
case ':':
- if (this.props.menuCallback) {
+ if (this._props.menuCallback) {
e.stopPropagation();
- this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
+ this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
break;
}
default:
- if (this.props.textCallback?.(e.key)) {
+ if (this._props.textCallback?.(e.key)) {
e.stopPropagation();
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
}
}
};
@action
onClick = (e: React.MouseEvent) => {
- if (this.props.editing !== false) {
+ if (this._props.editing !== false) {
e.nativeEvent.stopPropagation();
- if (this._ref.current && this.props.showMenuOnLoad) {
- this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
+ if (this._ref.current && this._props.showMenuOnLoad) {
+ this._props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
} else {
this._editing = true;
- this.props.isEditingCallback?.(true);
+ this._props.isEditingCallback?.(true);
}
// e.stopPropagation();
}
@@ -186,17 +190,17 @@ export class EditableView extends React.Component<EditableProps> {
@action
finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
- if (this.props.SetValue(value, shiftDown, enterKey)) {
+ if (this._props.SetValue(value, shiftDown, enterKey)) {
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
} else {
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
!lostFocus &&
setTimeout(
action(() => {
this._editing = true;
- this.props.isEditingCallback?.(true);
+ this._props.isEditingCallback?.(true);
}),
0
);
@@ -215,30 +219,32 @@ export class EditableView extends React.Component<EditableProps> {
};
renderEditor() {
- return this.props.autosuggestProps ? (
+ return this._props.autosuggestProps ? (
<Autosuggest
- {...this.props.autosuggestProps.autosuggestProps}
+ {...this._props.autosuggestProps.autosuggestProps}
inputProps={{
className: 'editableView-input',
onKeyDown: this.onKeyDown,
autoFocus: true,
+ // @ts-ignore
onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false),
onPointerDown: this.stopPropagation,
onClick: this.stopPropagation,
onPointerUp: this.stopPropagation,
onKeyPress: this.stopPropagation,
- value: this.props.autosuggestProps.value,
- onChange: this.props.autosuggestProps.onChange,
+ value: this._props.autosuggestProps.value,
+ // @ts-ignore
+ onChange: this._props.autosuggestProps.onChange,
}}
/>
- ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
+ ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
<input
className="editableView-input"
ref={r => (this._inputref = r)}
- style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
- placeholder={this.props.placeholder}
+ 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()}
+ defaultValue={this._props.GetValue()}
autoFocus={true}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
@@ -251,10 +257,10 @@ export class EditableView extends React.Component<EditableProps> {
<textarea
className="editableView-input"
ref={r => (this._inputref = r)}
- style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
- placeholder={this.props.placeholder}
+ 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()}
+ defaultValue={this._props.GetValue()}
autoFocus={true}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
@@ -267,9 +273,9 @@ export class EditableView extends React.Component<EditableProps> {
}
render() {
- const gval = this.props.GetValue()?.replace(/\n/g, '\\r\\n');
+ const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
if (this._editing && gval !== undefined) {
- return this.props.sizeToContent ? (
+ return this._props.sizeToContent ? (
<div style={{ display: 'grid', minWidth: 100 }}>
<div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div>
{this.renderEditor()}
@@ -278,30 +284,30 @@ export class EditableView extends React.Component<EditableProps> {
this.renderEditor()
);
}
- setTimeout(() => this.props.autosuggestProps?.resetValue());
- return this.props.contents instanceof ObjectField ? null : (
+ setTimeout(() => this._props.autosuggestProps?.resetValue());
+ return this._props.contents instanceof ObjectField ? null : (
<div
- className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
+ className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}
ref={this._ref}
style={{
- display: this.props.display, //
- textOverflow: this.props.overflow,
+ display: this._props.display, //
+ textOverflow: this._props.overflow,
minHeight: '10px',
- whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line',
- height: this.props.height,
- maxHeight: this.props.maxHeight,
- fontStyle: this.props.fontStyle,
- fontSize: this.props.fontSize,
+ whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line',
+ height: this._props.height,
+ maxHeight: this._props.maxHeight,
+ fontStyle: this._props.fontStyle,
+ fontSize: this._props.fontSize,
}}
//onPointerDown={this.stopPropagation}
onClick={this.onClick}
- placeholder={this.props.placeholder}>
+ placeholder={this._props.placeholder}>
<span
style={{
- fontStyle: this.props.fontStyle,
- fontSize: this.props.fontSize,
+ fontStyle: this._props.fontStyle,
+ fontSize: this._props.fontSize,
}}>
- {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+ {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : this._props.placeholder?.valueOf()}
</span>
</div>
);
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index cb5c9b085..a3c4350d9 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { action, computed, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
@@ -21,7 +21,7 @@ import { List } from '../../fields/List';
import { emptyFunction } from '../../Utils';
interface filterProps {
- rootDoc: Doc;
+ Document: Doc;
}
@observer
@@ -34,7 +34,7 @@ export class FilterPanel extends React.Component<filterProps> {
* @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
*/
@computed get targetDoc() {
- return this.props.rootDoc;
+ return this.props.Document;
}
@computed get targetDocChildKey() {
const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc);
@@ -113,7 +113,7 @@ export class FilterPanel extends React.Component<filterProps> {
// }
gatherFieldValues(childDocs: Doc[], facetKey: string) {
- const valueSet = new Set<string>(StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
+ const valueSet = new Set<string>(StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
let rtFields = 0;
let subDocs = childDocs;
if (subDocs.length > 0) {
@@ -224,7 +224,7 @@ export class FilterPanel extends React.Component<filterProps> {
facetValues = (facetHeader: string) => {
const allCollectionDocs = new Set<Doc>();
SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>([...StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
+ const set = new Set<string>([...StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
if (facetHeader === 'tags')
allCollectionDocs.forEach(child =>
StrListCast(child[facetHeader])
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index db6c00426..33cda6a62 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,6 +1,6 @@
-import React = require('react');
+import * as React from 'react';
import * as fitCurve from 'fit-curve';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx';
import { observer } from 'mobx-react';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
@@ -14,7 +14,6 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { Transform } from '../util/Transform';
import './GestureOverlay.scss';
-import { InkTranscription } from './InkTranscription';
import {
ActiveArrowEnd,
ActiveArrowScale,
@@ -32,12 +31,13 @@ import {
} from './InkingStroke';
import { checkInksToGroup } from './global/globalScripts';
import { DocumentView } from './nodes/DocumentView';
+import { ObservableReactComponent } from './ObservableReactComponent';
interface GestureOverlayProps {
isActive: boolean;
}
@observer
-export class GestureOverlay extends React.Component<React.PropsWithChildren<GestureOverlayProps>> {
+export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
static Instance: GestureOverlay;
static Instances: GestureOverlay[] = [];
@@ -85,7 +85,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
constructor(props: any) {
super(props);
-
+ makeObservable(this);
GestureOverlay.Instances.push(this);
}
@@ -93,9 +93,9 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
}
- componentDidMount = () => {
+ componentDidMount() {
GestureOverlay.Instance = this;
- };
+ }
@action
onPointerDown = (e: React.PointerEvent) => {
@@ -178,7 +178,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
newPoints.pop();
const controlPoints: { X: number; Y: number }[] = [];
- const bezierCurves = fitCurve(newPoints, 10);
+ const bezierCurves = (fitCurve as any)(newPoints, 10);
for (const curve of bezierCurves) {
controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
@@ -384,9 +384,9 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
return GestureOverlay.getBounds(this._points);
}
- @computed get elements() {
+ get elements() {
const selView = GestureOverlay.DownDocView;
- const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._freeform_scale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1);
+ const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?._props.ScreenToLocalTransform().Scale || 1);
const rect = this._overlayRef.current?.getBoundingClientRect();
const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
B.left = B.left - width / 2;
@@ -466,10 +466,8 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
this._clipboardDoc = (
<DocumentView
Document={doc}
- DataDoc={undefined}
addDocument={undefined}
addDocTab={returnFalse}
- rootSelected={returnTrue}
pinToPres={emptyFunction}
removeDocument={undefined}
ScreenToLocalTransform={this.screenToLocalTransform}
@@ -502,7 +500,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
render() {
return (
- <div className="gestureOverlay-cont" style={{ pointerEvents: this.props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
+ <div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
{this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
{this.elements}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 87f81fe76..4015fb321 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,5 +1,5 @@
import { random } from 'lodash';
-import { action, runInAction } from 'mobx';
+import { action } from 'mobx';
import { Doc, DocListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { InkTool } from '../../fields/InkField';
@@ -15,7 +15,6 @@ import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
@@ -91,8 +90,8 @@ export class KeyManager {
});
nudge = (x: number, y: number, label: string) => {
- const nudgeable = SelectionManager.Views().some(dv => dv.CollectionFreeFormDocumentView?.nudge);
- nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label);
+ const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge);
+ nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label);
return { stopPropagation: nudgeable, preventDefault: nudgeable };
};
@@ -100,18 +99,18 @@ export class KeyManager {
switch (keyname) {
case 'u':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- const ungroupings = SelectionManager.Views();
+ const ungroupings = SelectionManager.Views;
UndoManager.RunInBatch(() => ungroupings.map(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 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())
+ collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs)
, 'grouping');
break;
}
@@ -127,7 +126,7 @@ export class KeyManager {
Doc.ActiveTool = InkTool.None;
DragManager.CompleteWindowDrag?.(true);
var doDeselect = true;
- if (SnappingManager.GetIsDragging()) {
+ if (SnappingManager.IsDragging) {
DragManager.AbortDrag();
} else if (CollectionDockingView.Instance?.HasFullScreen) {
CollectionDockingView.Instance?.CloseFullScreen();
@@ -146,7 +145,6 @@ export class KeyManager {
SharingManager.Instance.close();
if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
GroupManager.Instance.close();
- CollectionFreeFormViewChrome.Instance?.clearKeepPrimitiveMode();
window.getSelection()?.empty();
document.body.focus();
break;
@@ -184,14 +182,14 @@ 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;
@@ -210,7 +208,7 @@ export class KeyManager {
switch (keyname) {
case 'Æ’':
case 'f':
- const dv = SelectionManager.Views()?.[0];
+ const dv = SelectionManager.Views?.[0];
UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float');
}
@@ -258,8 +256,8 @@ export class KeyManager {
}
break;
case 'f':
- if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) {
- SelectionManager.Views()[0].ComponentView?.search?.('', false, false);
+ if (SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView?.search) {
+ SelectionManager.Views[0].ComponentView?.search?.('', false, false);
} else {
const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
if (searchBtn) {
@@ -301,17 +299,11 @@ export class KeyManager {
preventDefault = false;
break;
case 'x':
- if (SelectionManager.Views().length) {
+ if (SelectionManager.Views.length) {
const bds = DocumentDecorations.Instance.Bounds;
- const pt = SelectionManager.Views()[0]
- .props.ScreenToLocalTransform()
- .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
- const text =
- `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
- SelectionManager.Views()
- .map(dv => dv.Document[Id])
- .join(':');
- SelectionManager.Views().length && navigator.clipboard.writeText(text);
+ const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
+ SelectionManager.Views.length && navigator.clipboard.writeText(text);
DocumentDecorations.Instance.onCloseClick(true);
stopPropagation = false;
preventDefault = false;
@@ -320,15 +312,9 @@ export class KeyManager {
case 'c':
if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
const bds = DocumentDecorations.Instance.Bounds;
- const pt = SelectionManager.Views()[0]
- .props.ScreenToLocalTransform()
- .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
- const text =
- `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
- SelectionManager.Views()
- .map(dv => dv.Document[Id])
- .join(':');
- SelectionManager.Views().length && navigator.clipboard.writeText(text);
+ const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
+ SelectionManager.Views.length && navigator.clipboard.writeText(text);
stopPropagation = false;
}
preventDefault = false;
@@ -346,7 +332,7 @@ export class KeyManager {
if (!plain) return;
const clone = plain.startsWith('__DashCloneId(');
const docids = plain.split(':'); // hack! docids[0] is the top left of the selection rectangle
- const addDocument = SelectionManager.Views().lastElement()?.ComponentView?.addDocument;
+ const addDocument = SelectionManager.Views.lastElement()?.ComponentView?.addDocument;
if (addDocument && (plain.startsWith('__DashDocId(') || clone)) {
Doc.Paste(docids.slice(1), clone, addDocument);
}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index e5141b7f4..a5c2d99d2 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
@@ -208,8 +208,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
- InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2, v1n, e.shiftKey);
- InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
+ InkStrokeProperties.Instance.stretchInk(SelectionManager.Views, scaling, p2, v1n, e.shiftKey);
+ InkStrokeProperties.Instance.rotateInk(SelectionManager.Views, angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
return false;
}),
action(() => {
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 62165bc48..a628c7120 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,5 +1,5 @@
import { Bezier } from 'bezier-js';
-import { action, observable, reaction } from 'mobx';
+import { action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { Doc, NumListCast, Opt } from '../../fields/Doc';
import { InkData, InkField, InkTool, PointData } from '../../fields/InkField';
import { List } from '../../fields/List';
@@ -12,7 +12,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { undoBatch } from '../util/UndoManager';
import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
-import _ = require('lodash');
+import * as _ from 'lodash';
export class InkStrokeProperties {
static _Instance: InkStrokeProperties | undefined;
@@ -25,6 +25,7 @@ export class InkStrokeProperties {
constructor() {
InkStrokeProperties._Instance = this;
+ makeObservable(this);
reaction(
() => this._controlButton,
button => button && (Doc.ActiveTool = InkTool.None)
@@ -49,7 +50,7 @@ export class InkStrokeProperties {
(strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(
action(inkView => {
if (!requireCurrPoint || this._currentPoint !== -1) {
- const doc = inkView.rootDoc;
+ const doc = inkView.Document;
if (doc.type === DocumentType.INK && doc.width && doc.height) {
const ink = Cast(doc.stroke, InkField)?.inkData;
if (ink) {
@@ -83,10 +84,9 @@ export class InkStrokeProperties {
* @param control The list of all control points of the ink.
*/
@undoBatch
- @action
addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const doc = view.rootDoc;
+ 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);
const splicepts = [...newsegs.left.points, ...newsegs.right.points];
@@ -94,7 +94,7 @@ 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)));
- this._currentPoint = -1;
+ runInAction(() => (this._currentPoint = -1));
return controls;
});
@@ -154,12 +154,11 @@ export class InkStrokeProperties {
* Deletes the current control point of the selected ink instance.
*/
@undoBatch
- @action
deletePoints = (inkView: DocumentView, preserve: boolean) =>
this.applyFunction(
inkView,
(view: DocumentView, ink: InkData) => {
- const doc = view.rootDoc;
+ const doc = view.Document;
const newPoints = ink.slice();
const brokenIndices = NumListCast(doc.brokenInkIndices);
if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
@@ -187,7 +186,7 @@ export class InkStrokeProperties {
}
}
doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
- this._currentPoint = -1;
+ runInAction(() => (this._currentPoint = -1));
return newPoints.length < 4 ? undefined : newPoints;
},
true
@@ -200,7 +199,6 @@ export class InkStrokeProperties {
* @param scrpt The center point of the rotation in screen coordinates
*/
@undoBatch
- @action
rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt);
@@ -222,7 +220,6 @@ export class InkStrokeProperties {
* @param scrpt The center point of the rotation in screen coordinates
*/
@undoBatch
- @action
stretchInk = (inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => {
const ptFromScreen = view.ComponentView?.ptFromScreen;
@@ -243,7 +240,6 @@ export class InkStrokeProperties {
* Handles the movement/scaling of a control point.
*/
@undoBatch
- @action
moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const order = controlIndex % 4;
@@ -335,7 +331,7 @@ export class InkStrokeProperties {
* Handles the movement/scaling of a control point.
*/
snapControl = (inkView: DocumentView, controlIndex: number) => {
- const inkDoc = inkView.rootDoc;
+ const inkDoc = inkView.Document;
const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
if (ink) {
@@ -378,9 +374,9 @@ export class InkStrokeProperties {
.filter(doc => doc.type === DocumentType.INK)
.forEach(doc => {
const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
- const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
+ const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.Document ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
if (snapped && snapped.distance < snapData.distance) {
- const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+ const snappedInkPt = doc === inkView.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) {
snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
@@ -397,7 +393,7 @@ export class InkStrokeProperties {
*/
snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const doc = view.rootDoc;
+ const doc = view.Document;
const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []);
const ind = brokenIndices.findIndex(value => value === controlIndex);
if (ind !== -1) {
@@ -456,10 +452,9 @@ export class InkStrokeProperties {
* Handles the movement/scaling of a handle point.
*/
@undoBatch
- @action
moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const doc = view.rootDoc;
+ const doc = view.Document;
const closed = InkingStroke.IsClosed(ink);
const oldHandlePoint = ink[handleIndex];
const oppositeHandlePoint = ink[oppositeHandleIndex];
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index 65f6a6dfa..a00c26a07 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { action } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 17136a737..b2ac208ca 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -1,350 +1,350 @@
-import * as iink from 'iink-js';
-import { action, observable } from 'mobx';
-import * as React from 'react';
-import { Doc, DocListCast } from '../../fields/Doc';
-import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { Cast, DateCast, NumCast } from '../../fields/Types';
-import { aggregateBounds } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { InkingStroke } from './InkingStroke';
-import './InkTranscription.scss';
-
-/**
- * Class component that handles inking in writing mode
- */
-export class InkTranscription extends React.Component {
- static Instance: InkTranscription;
-
- @observable _mathRegister: any;
- @observable _mathRef: any;
- @observable _textRegister: any;
- @observable _textRef: any;
- private lastJiix: any;
- private currGroup?: Doc;
-
- constructor(props: Readonly<{}>) {
- super(props);
-
- InkTranscription.Instance = this;
- }
-
- componentWillUnmount() {
- this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
- this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
- }
-
- @action
- setMathRef = (r: any) => {
- if (!this._mathRegister) {
- this._mathRegister = r
- ? iink.register(r, {
- recognitionParams: {
- type: 'MATH',
- protocol: 'WEBSOCKET',
- server: {
- host: 'cloud.myscript.com',
- applicationKey: process.env.IINKJS_APP,
- hmacKey: process.env.IINKJS_HMAC,
- websocket: {
- pingEnabled: false,
- autoReconnect: true,
- },
- },
- iink: {
- math: {
- mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'],
- },
- export: {
- jiix: {
- strokes: true,
- },
- },
- },
- },
- })
- : null;
- }
-
- r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
-
- return (this._mathRef = r);
- };
-
- @action
- setTextRef = (r: any) => {
- if (!this._textRegister) {
- this._textRegister = r
- ? iink.register(r, {
- recognitionParams: {
- type: 'TEXT',
- protocol: 'WEBSOCKET',
- server: {
- host: 'cloud.myscript.com',
- applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
- hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
- websocket: {
- pingEnabled: false,
- autoReconnect: true,
- },
- },
- iink: {
- text: {
- mimeTypes: ['text/plain'],
- },
- export: {
- jiix: {
- strokes: true,
- },
- },
- },
- },
- })
- : null;
- }
-
- r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
-
- return (this._textRef = r);
- };
-
- /**
- * Handles processing Dash Doc data for ink transcription.
- *
- * @param groupDoc the group which contains the ink strokes we want to transcribe
- * @param inkDocs the ink docs contained within the selected group
- * @param math boolean whether to do math transcription or not
- */
- transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => {
- if (!groupDoc) return;
- const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
-
- const strokes: InkData[] = [];
- const times: number[] = [];
- validInks
- .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
- .forEach(i => {
- const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
- const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
- strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
- times.push(DateCast(i.author_date).getDate().getTime());
- });
-
- this.currGroup = groupDoc;
-
- const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
- const processGestures = false;
-
- if (math) {
- this._mathRef.editor.pointerEvents(pointerData, processGestures);
- } else {
- this._textRef.editor.pointerEvents(pointerData, processGestures);
- }
- };
-
- /**
- * Converts the Dash Ink Data to JSON.
- *
- * @param stroke The dash ink data
- * @param time the time of the stroke
- * @returns json object representation of ink data
- */
- inkJSON = (stroke: InkData, time: number) => {
- return {
- pointerType: 'PEN',
- pointerId: 1,
- x: stroke.map(point => point.X),
- y: stroke.map(point => point.Y),
- t: new Array(stroke.length).fill(time),
- p: new Array(stroke.length).fill(1.0),
- };
- };
-
- /**
- * Creates subgroups for each word for the whole text transcription
- * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs)
- */
- subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
- // iterate through the keys of wordInkDocMap
- wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
- const selected = inkDocs.slice();
- if (!selected) {
- return;
- }
- const ctx = await Cast(selected[0].embedContainer, Doc);
- if (!ctx) {
- return;
- }
- const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
-
- if (!docView) return;
- const marqViewRef = docView._marqueeViewRef.current;
- if (!marqViewRef) return;
- this.groupInkDocs(selected, docView, word);
- });
- };
-
- /**
- * Event listener function for when the 'exported' event is heard.
- *
- * @param e the event objects
- * @param ref the ref to the editor
- */
- exportInk = (e: any, ref: any) => {
- const exports = e.detail.exports;
- if (exports) {
- if (exports['application/x-latex']) {
- const latex = exports['application/x-latex'];
- if (this.currGroup) {
- this.currGroup.text = latex;
- this.currGroup.title = latex;
- }
-
- ref.editor.clear();
- } else if (exports['text/plain']) {
- if (exports['application/vnd.myscript.jiix']) {
- this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
- // map timestamp to strokes
- const timestampWord = new Map<number, string>();
- this.lastJiix.words.map((word: any) => {
- if (word.items) {
- word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => {
- const ms = Date.parse(i.timestamp);
- timestampWord.set(ms, word.label);
- });
- }
- });
-
- const wordInkDocMap = new Map<string, Doc[]>();
- if (this.currGroup) {
- const docList = DocListCast(this.currGroup.data);
- docList.forEach((inkDoc: Doc) => {
- // just having the times match up and be a unique value (actual timestamp doesn't matter)
- const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000;
- const word = timestampWord.get(ms);
- if (!word) {
- return;
- }
- const entry = wordInkDocMap.get(word);
- if (entry) {
- entry.push(inkDoc);
- wordInkDocMap.set(word, entry);
- } else {
- const newEntry = [inkDoc];
- wordInkDocMap.set(word, newEntry);
- }
- });
- if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
- }
- }
- const text = exports['text/plain'];
-
- if (this.currGroup) {
- this.currGroup.transcription = text;
- this.currGroup.title = text.split('\n')[0];
- }
-
- ref.editor.clear();
- }
- }
- };
-
- /**
- * Creates the ink grouping once the user leaves the writing mode.
- */
- createInkGroup() {
- // TODO nda - if document being added to is a inkGrouping then we can just add to that group
- if (Doc.ActiveTool === InkTool.Write) {
- CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
- // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
- const selected = ffView.unprocessedDocs;
- const newCollection = this.groupInkDocs(
- selected.filter(doc => doc.embedContainer),
- ffView
- );
- ffView.unprocessedDocs = [];
-
- InkTranscription.Instance.transcribeInk(newCollection, selected, false);
- });
- }
- CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
- }
-
- /**
- * Creates the groupings for a given list of ink docs on a specific doc view
- * @param selected: the list of ink docs to create a grouping of
- * @param docView: the view in which we want the grouping to be created
- * @param word: optional param if the group we are creating is a word (subgrouping individual words)
- * @returns a new collection Doc or undefined if the grouping fails
- */
- groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
- const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
-
- // calculate the necessary bounds from the selected ink docs
- selected.map(
- action(d => {
- const x = NumCast(d.x);
- const y = NumCast(d.y);
- const width = NumCast(d._width);
- const height = NumCast(d._height);
- bounds.push({ x, y, width, height });
- })
- );
-
- // calculate the aggregated bounds
- const aggregBounds = aggregateBounds(bounds, 0, 0);
- const marqViewRef = docView._marqueeViewRef.current;
-
- // set the vals for bounds in marqueeView
- if (marqViewRef) {
- marqViewRef._downX = aggregBounds.x;
- marqViewRef._downY = aggregBounds.y;
- marqViewRef._lastX = aggregBounds.r;
- marqViewRef._lastY = aggregBounds.b;
- }
-
- // map through all the selected ink strokes and create the groupings
- selected.map(
- action(d => {
- const dx = NumCast(d.x);
- const dy = NumCast(d.y);
- delete d.x;
- delete d.y;
- delete d.activeFrame;
- delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- // calculate pos based on bounds
- if (marqViewRef?.Bounds) {
- d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
- d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
- }
- return d;
- })
- );
- docView.props.removeDocument?.(selected);
- // Gets a collection based on the selected nodes using a marquee view ref
- const newCollection = marqViewRef?.getCollection(selected, undefined, true);
- if (newCollection) {
- newCollection.width = NumCast(newCollection._width);
- newCollection.height = NumCast(newCollection._height);
- // if the grouping we are creating is an individual word
- if (word) {
- newCollection.title = word;
- }
- }
-
- // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
- newCollection && docView.props.addDocument?.(newCollection);
- return newCollection;
- }
-
- render() {
- return (
- <div className="ink-transcription">
- <div className="math-editor" ref={this.setMathRef} touch-action="none"></div>
- <div className="text-editor" ref={this.setTextRef} touch-action="none"></div>
- </div>
- );
- }
-}
+// import * as iink from 'iink-js';
+// import { action, observable } from 'mobx';
+// import * as React from 'react';
+// import { Doc, DocListCast } from '../../fields/Doc';
+// import { InkData, InkField, InkTool } from '../../fields/InkField';
+// import { Cast, DateCast, NumCast } from '../../fields/Types';
+// import { aggregateBounds } from '../../Utils';
+// import { DocumentType } from '../documents/DocumentTypes';
+// import { DocumentManager } from '../util/DocumentManager';
+// import { CollectionFreeFormView } from './collections/collectionFreeForm';
+// import { InkingStroke } from './InkingStroke';
+// import './InkTranscription.scss';
+
+// /**
+// * Class component that handles inking in writing mode
+// */
+// export class InkTranscription extends React.Component {
+// static Instance: InkTranscription;
+
+// @observable _mathRegister: any;
+// @observable _mathRef: any;
+// @observable _textRegister: any;
+// @observable _textRef: any;
+// private lastJiix: any;
+// private currGroup?: Doc;
+
+// constructor(props: Readonly<{}>) {
+// super(props);
+
+// InkTranscription.Instance = this;
+// }
+
+// componentWillUnmount() {
+// this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+// this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+// }
+
+// @action
+// setMathRef = (r: any) => {
+// if (!this._mathRegister) {
+// this._mathRegister = r
+// ? iink.register(r, {
+// recognitionParams: {
+// type: 'MATH',
+// protocol: 'WEBSOCKET',
+// server: {
+// host: 'cloud.myscript.com',
+// applicationKey: process.env.IINKJS_APP,
+// hmacKey: process.env.IINKJS_HMAC,
+// websocket: {
+// pingEnabled: false,
+// autoReconnect: true,
+// },
+// },
+// iink: {
+// math: {
+// mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'],
+// },
+// export: {
+// jiix: {
+// strokes: true,
+// },
+// },
+// },
+// },
+// })
+// : null;
+// }
+
+// r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+
+// return (this._mathRef = r);
+// };
+
+// @action
+// setTextRef = (r: any) => {
+// if (!this._textRegister) {
+// this._textRegister = r
+// ? iink.register(r, {
+// recognitionParams: {
+// type: 'TEXT',
+// protocol: 'WEBSOCKET',
+// server: {
+// host: 'cloud.myscript.com',
+// applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
+// hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
+// websocket: {
+// pingEnabled: false,
+// autoReconnect: true,
+// },
+// },
+// iink: {
+// text: {
+// mimeTypes: ['text/plain'],
+// },
+// export: {
+// jiix: {
+// strokes: true,
+// },
+// },
+// },
+// },
+// })
+// : null;
+// }
+
+// r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+
+// return (this._textRef = r);
+// };
+
+// /**
+// * Handles processing Dash Doc data for ink transcription.
+// *
+// * @param groupDoc the group which contains the ink strokes we want to transcribe
+// * @param inkDocs the ink docs contained within the selected group
+// * @param math boolean whether to do math transcription or not
+// */
+// transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => {
+// if (!groupDoc) return;
+// const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
+
+// const strokes: InkData[] = [];
+// const times: number[] = [];
+// validInks
+// .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
+// .forEach(i => {
+// const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
+// const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
+// strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
+// times.push(DateCast(i.author_date).getDate().getTime());
+// });
+
+// this.currGroup = groupDoc;
+
+// const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
+// const processGestures = false;
+
+// if (math) {
+// this._mathRef.editor.pointerEvents(pointerData, processGestures);
+// } else {
+// this._textRef.editor.pointerEvents(pointerData, processGestures);
+// }
+// };
+
+// /**
+// * Converts the Dash Ink Data to JSON.
+// *
+// * @param stroke The dash ink data
+// * @param time the time of the stroke
+// * @returns json object representation of ink data
+// */
+// inkJSON = (stroke: InkData, time: number) => {
+// return {
+// pointerType: 'PEN',
+// pointerId: 1,
+// x: stroke.map(point => point.X),
+// y: stroke.map(point => point.Y),
+// t: new Array(stroke.length).fill(time),
+// p: new Array(stroke.length).fill(1.0),
+// };
+// };
+
+// /**
+// * Creates subgroups for each word for the whole text transcription
+// * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs)
+// */
+// subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
+// // iterate through the keys of wordInkDocMap
+// wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
+// const selected = inkDocs.slice();
+// if (!selected) {
+// return;
+// }
+// const ctx = await Cast(selected[0].embedContainer, Doc);
+// if (!ctx) {
+// return;
+// }
+// const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
+
+// if (!docView) return;
+// const marqViewRef = docView._marqueeViewRef.current;
+// if (!marqViewRef) return;
+// this.groupInkDocs(selected, docView, word);
+// });
+// };
+
+// /**
+// * Event listener function for when the 'exported' event is heard.
+// *
+// * @param e the event objects
+// * @param ref the ref to the editor
+// */
+// exportInk = (e: any, ref: any) => {
+// const exports = e.detail.exports;
+// if (exports) {
+// if (exports['application/x-latex']) {
+// const latex = exports['application/x-latex'];
+// if (this.currGroup) {
+// this.currGroup.text = latex;
+// this.currGroup.title = latex;
+// }
+
+// ref.editor.clear();
+// } else if (exports['text/plain']) {
+// if (exports['application/vnd.myscript.jiix']) {
+// this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
+// // map timestamp to strokes
+// const timestampWord = new Map<number, string>();
+// this.lastJiix.words.map((word: any) => {
+// if (word.items) {
+// word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => {
+// const ms = Date.parse(i.timestamp);
+// timestampWord.set(ms, word.label);
+// });
+// }
+// });
+
+// const wordInkDocMap = new Map<string, Doc[]>();
+// if (this.currGroup) {
+// const docList = DocListCast(this.currGroup.data);
+// docList.forEach((inkDoc: Doc) => {
+// // just having the times match up and be a unique value (actual timestamp doesn't matter)
+// const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000;
+// const word = timestampWord.get(ms);
+// if (!word) {
+// return;
+// }
+// const entry = wordInkDocMap.get(word);
+// if (entry) {
+// entry.push(inkDoc);
+// wordInkDocMap.set(word, entry);
+// } else {
+// const newEntry = [inkDoc];
+// wordInkDocMap.set(word, newEntry);
+// }
+// });
+// if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
+// }
+// }
+// const text = exports['text/plain'];
+
+// if (this.currGroup) {
+// this.currGroup.transcription = text;
+// this.currGroup.title = text.split('\n')[0];
+// }
+
+// ref.editor.clear();
+// }
+// }
+// };
+
+// /**
+// * Creates the ink grouping once the user leaves the writing mode.
+// */
+// createInkGroup() {
+// // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+// if (Doc.ActiveTool === InkTool.Write) {
+// CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+// // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+// const selected = ffView.unprocessedDocs;
+// const newCollection = this.groupInkDocs(
+// selected.filter(doc => doc.embedContainer),
+// ffView
+// );
+// ffView.unprocessedDocs = [];
+
+// InkTranscription.Instance.transcribeInk(newCollection, selected, false);
+// });
+// }
+// CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+// }
+
+// /**
+// * Creates the groupings for a given list of ink docs on a specific doc view
+// * @param selected: the list of ink docs to create a grouping of
+// * @param docView: the view in which we want the grouping to be created
+// * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+// * @returns a new collection Doc or undefined if the grouping fails
+// */
+// groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+// const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
+
+// // calculate the necessary bounds from the selected ink docs
+// selected.map(
+// action(d => {
+// const x = NumCast(d.x);
+// const y = NumCast(d.y);
+// const width = NumCast(d._width);
+// const height = NumCast(d._height);
+// bounds.push({ x, y, width, height });
+// })
+// );
+
+// // calculate the aggregated bounds
+// const aggregBounds = aggregateBounds(bounds, 0, 0);
+// const marqViewRef = docView._marqueeViewRef.current;
+
+// // set the vals for bounds in marqueeView
+// if (marqViewRef) {
+// marqViewRef._downX = aggregBounds.x;
+// marqViewRef._downY = aggregBounds.y;
+// marqViewRef._lastX = aggregBounds.r;
+// marqViewRef._lastY = aggregBounds.b;
+// }
+
+// // map through all the selected ink strokes and create the groupings
+// selected.map(
+// action(d => {
+// const dx = NumCast(d.x);
+// const dy = NumCast(d.y);
+// delete d.x;
+// delete d.y;
+// delete d.activeFrame;
+// delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+// delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+// // calculate pos based on bounds
+// if (marqViewRef?.Bounds) {
+// d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+// d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+// }
+// return d;
+// })
+// );
+// docView.props.removeDocument?.(selected);
+// // Gets a collection based on the selected nodes using a marquee view ref
+// const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+// if (newCollection) {
+// newCollection.width = NumCast(newCollection._width);
+// newCollection.height = NumCast(newCollection._height);
+// // if the grouping we are creating is an individual word
+// if (word) {
+// newCollection.title = word;
+// }
+// }
+
+// // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+// newCollection && docView.props.addDocument?.(newCollection);
+// return newCollection;
+// }
+
+// render() {
+// return (
+// <div className="ink-transcription">
+// <div className="math-editor" ref={this.setMathRef} touch-action="none"></div>
+// <div className="text-editor" ref={this.setTextRef} touch-action="none"></div>
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index cf565abc8..9c96b4563 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,7 +20,7 @@
Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
*/
-import React = require('react');
+import * as React from 'react';
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
@@ -36,7 +36,6 @@ import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import { ViewBoxBaseComponent } from './DocComponent';
-import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
import { Colors } from './global/globalEnums';
import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
@@ -47,8 +46,7 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { PinProps, PresBox } from './nodes/trails';
import { StyleProp } from './StyleProvider';
-import Color = require('color');
-
+const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
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)
@@ -82,24 +80,24 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
- if (subAnchor !== this.rootDoc && subAnchor) return subAnchor;
+ if (subAnchor !== this.Document && subAnchor) return subAnchor;
- if (!addAsAnnotation && !pinProps) return this.rootDoc;
+ if (!addAsAnnotation && !pinProps) return this.Document;
const anchor = Docs.Create.ConfigDocument({
- title: 'Ink anchor:' + this.rootDoc.title,
+ title: 'Ink anchor:' + this.Document.title,
// set presentation timing for restoring shape
presentation_duration: 1100,
presentation_transition: 1000,
- annotationOn: this.rootDoc,
+ annotationOn: this.Document,
});
if (anchor) {
anchor.backgroundColor = 'transparent';
// /* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.Document);
return anchor;
}
- return this.rootDoc;
+ return this.Document;
};
/**
@@ -141,7 +139,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
public static toggleMask = action((inkDoc: Doc) => {
inkDoc.stroke_isInkMask = !inkDoc.stroke_isInkMask;
});
- @observable controlUndo: UndoManager.Batch | undefined;
+ @observable controlUndo: UndoManager.Batch | undefined = undefined;
/**
* Drags the a simple bezier segment of the stroke.
* Also adds a control point when double clicking on the stroke.
@@ -245,8 +243,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
* factor for converting between ink and screen space.
*/
inkScaledData = () => {
- const inkData = Cast(this.rootDoc[this.fieldKey], InkField)?.inkData ?? [];
- const inkStrokeWidth = NumCast(this.rootDoc.stroke_width, 1);
+ const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2;
@@ -322,7 +320,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
const markerScale = NumCast(this.layoutDoc.stroke_markerScale);
- return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? (
+ return SnappingManager.IsDragging ? null : !InkStrokeProperties.Instance._controlButton ? (
!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
<div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
<InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={this.startPt} endPt={this.endPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
@@ -362,7 +360,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
@computed get fillColor() {
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
- return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FillColor) ?? 'transparent';
+ 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();
@@ -387,7 +385,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
});
}
- const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+ const highlight = !this.controlUndo && this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
const highlightIndex = highlight?.highlightIndex;
const highlightColor = !this.props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
const color = StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
@@ -457,7 +455,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
onContextMenu: () => {
const cm = ContextMenu.Instance;
!Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
- cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.rootDoc), icon: 'paint-brush' });
+ cm?.addItem({ description: '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' });
},
};
@@ -466,7 +464,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
<svg
className="inkStroke"
style={{
- transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 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,
}}
@@ -474,7 +472,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
{clickableLine(this.onPointerDown, isInkMask)}
{isInkMask ? null : inkLine}
</svg>
- {!closed || (!RTFCast(this.rootDoc.text)?.Text && (!this.props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+ {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this.props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
<div
className="inkStroke-text"
style={{
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index 13d52db88..e996fc946 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -1,6 +1,6 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import "./KeyphraseQueryView.scss";
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import './KeyphraseQueryView.scss';
// tslint:disable-next-line: class-name
export interface KP_Props {
@@ -8,7 +8,7 @@ export interface KP_Props {
}
@observer
-export class KeyphraseQueryView extends React.Component<KP_Props>{
+export class KeyphraseQueryView extends React.Component<KP_Props> {
constructor(props: KP_Props) {
super(props);
}
@@ -22,13 +22,17 @@ export class KeyphraseQueryView extends React.Component<KP_Props>{
<form>
{keyterms.map((kp: string) => {
//return (<p>{"-" + kp}</p>);
- return (<p><label>
- <input name="query" type="radio" />
- <span>{kp}</span>
- </label></p>);
+ return (
+ <p>
+ <label>
+ <input name="query" type="radio" />
+ <span>{kp}</span>
+ </label>
+ </p>
+ );
})}
</form>
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index c91f8455a..7577f2935 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,13 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
import { InkTool } from '../../fields/InkField';
import { Cast, NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, Utils } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
import { LinkManager } from '../util/LinkManager';
@@ -21,6 +21,7 @@ import { GestureOverlay } from './GestureOverlay';
import './LightboxView.scss';
import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
+import { ObservableReactComponent } from './ObservableReactComponent';
interface LightboxViewProps {
PanelWidth: number;
@@ -31,7 +32,7 @@ interface LightboxViewProps {
const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_scrollTop', 'layout_fieldKey'];
type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore
@observer
-export class LightboxView extends React.Component<LightboxViewProps> {
+export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
public static IsLightboxDocView(path: DocumentView[]) { return (path ?? []).includes(LightboxView.Instance?._docView!); } // prettier-ignore
public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
static Instance: LightboxView;
@@ -45,18 +46,18 @@ export class LightboxView extends React.Component<LightboxViewProps> {
private _savedState: LightboxSavedState = {};
private _history: { doc: Doc; target?: Doc }[] = [];
@observable private _future: Doc[] = [];
- @observable private _layoutTemplate: Opt<Doc>;
- @observable private _layoutTemplateString: Opt<string>;
- @observable private _doc: Opt<Doc>;
- @observable private _docTarget: Opt<Doc>;
- @observable private _docView: Opt<DocumentView>;
+ @observable private _layoutTemplate: Opt<Doc> = undefined;
+ @observable private _layoutTemplateString: Opt<string> = undefined;
+ @observable private _doc: Opt<Doc> = undefined;
+ @observable private _docTarget: Opt<Doc> = undefined;
+ @observable private _docView: Opt<DocumentView> = undefined;
- @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); } // prettier-ignore
- @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); } // prettier-ignore
+ @computed get leftBorder() { return Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]); } // prettier-ignore
+ @computed get topBorder() { return Math.min(this._props.PanelHeight / 4, this._props.maxBorder[1]); } // prettier-ignore
constructor(props: any) {
super(props);
- if (LightboxView.Instance) console.log('SDFSFASFASFSALFKJD:SLFJS:LDFJKS:LFJS:LDJFL:SDFJL:SDJF:LSJ');
+ makeObservable(this);
LightboxView.Instance = this;
}
@@ -174,8 +175,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
toggleExplore = () => (DocumentView.ExploreMode = !DocumentView.ExploreMode);
lightboxDoc = () => this._doc;
- lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
- lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
+ lightboxWidth = () => this._props.PanelWidth - this.leftBorder * 2;
+ lightboxHeight = () => this._props.PanelHeight - this.topBorder * 2;
lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
lightboxDocTemplate = () => this._layoutTemplate;
future = () => this._future;
@@ -187,7 +188,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
style={{
display: display ? '' : 'none',
left,
- width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ width: bottom !== undefined ? undefined : Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
bottom,
}}>
<div
@@ -247,7 +248,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
<DocumentView
ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
Document={this._doc}
- DataDoc={undefined}
PanelWidth={this.lightboxWidth}
PanelHeight={this.lightboxHeight}
LayoutTemplate={this.lightboxDocTemplate}
@@ -256,7 +256,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
styleProvider={DefaultStyleProvider}
ScreenToLocalTransform={this.lightboxScreenToLocal}
renderDepth={0}
- rootSelected={returnTrue}
docViewPath={returnEmptyDoclist}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
@@ -273,11 +272,11 @@ export class LightboxView extends React.Component<LightboxViewProps> {
</GestureOverlay>
</div>
- {this.renderNavBtn(0, undefined, this.props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length, this.previous)}
+ {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length, this.previous)}
{this.renderNavBtn(
- this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
undefined,
- this.props.PanelHeight / 2 - 12.5,
+ this._props.PanelHeight / 2 - 12.5,
'chevron-right',
this._doc && this._future.length,
this.next,
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index a403a10e3..02916e48e 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
@import 'nodeModuleOverrides';
:root {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 96bd52d39..17c21326d 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -22,7 +22,10 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
(async () => {
MainView.Live = window.location.search.includes('live');
- const root = ReactDOM.createRoot(document.getElementById('root')!);
+ const rootEle = document.getElementById('root');
+ if (!rootEle) return;
+ rootEle.style.zIndex = '0';
+ const root = ReactDOM.createRoot(rootEle);
root.render(<FieldLoader />);
window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4fb2ac279..28a0f7750 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module.scss';
@import 'nodeModuleOverrides';
html {
overscroll-behavior-x: none;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d69834f34..c71c72257 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,25 +3,24 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import 'browndash-components/dist/styles/global.min.css';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import 'normalize.css';
import * as React from 'react';
+import '../../../node_modules/browndash-components/dist/styles/global.min.css';
+import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
-import { Docs } from '../documents/Documents';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
-import { ReportManager } from '../util/reportManager/ReportManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
@@ -30,52 +29,55 @@ import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
-import { TimelineMenu } from './animationtimeline/TimelineMenu';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import { CollectionLinearView } from './collections/collectionLinear';
-import { CollectionMenu } from './collections/CollectionMenu';
-import { TabDocView } from './collections/TabDocView';
-import './collections/TreeView.scss';
+import { ReportManager } from '../util/reportManager/ReportManager';
import { ComponentDecorations } from './ComponentDecorations';
import { ContextMenu } from './ContextMenu';
import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
-import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
import { KeyManager } from './GlobalKeyHandler';
-import { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
-import { LinkMenu } from './linking/LinkMenu';
import './MainView.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { OverlayView } from './OverlayView';
+import { PreviewCursor } from './PreviewCursor';
+import { PropertiesView } from './PropertiesView';
+import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
+import { TimelineMenu } from './animationtimeline/TimelineMenu';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionMenu } from './collections/CollectionMenu';
+import { TabDocView } from './collections/TabDocView';
+import './collections/TreeView.scss';
+import { CollectionFreeFormLinksView } from './collections/collectionFreeForm';
+import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import { CollectionLinearView } from './collections/collectionLinear';
+import { LinkMenu } from './linking/LinkMenu';
import { AudioBox } from './nodes/AudioBox';
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
-import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
-import GenerativeFill from './nodes/generativeFill/GenerativeFill';
import { ImageBox } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
-import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
import { MapBox } from './nodes/MapBox/MapBox';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
+import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
import { PresBox } from './nodes/trails';
-import { OverlayView } from './OverlayView';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
-import { PreviewCursor } from './PreviewCursor';
-import { PropertiesView } from './PropertiesView';
-import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
-import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
+const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
+import { CalendarManager } from '../util/CalendarManager';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
-export class MainView extends React.Component {
+export class MainView extends ObservableReactComponent<{}> {
public static Instance: MainView;
public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -131,14 +133,14 @@ export class MainView extends React.Component {
}
headerBarDocWidth = () => this.mainDocViewWidth();
- headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+ headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0);
topMenuHeight = () => (this._hideUI ? 0 : 35);
topMenuWidth = returnZero; // value is ignored ...
leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
leftMenuHeight = () => this._dashUIHeight;
leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
leftMenuFlyoutHeight = () => this._dashUIHeight;
- propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0));
+ propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0));
propertiesHeight = () => this._dashUIHeight;
mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
@@ -146,7 +148,7 @@ export class MainView extends React.Component {
componentDidMount() {
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(),
+ () => SelectionManager.Views.slice(),
views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur()
);
const scriptTag = document.createElement('script');
@@ -230,8 +232,9 @@ export class MainView extends React.Component {
document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
}
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
+ makeObservable(this);
DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
MainView.Instance = this;
DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -600,19 +603,17 @@ export class MainView extends React.Component {
<DocumentView
key="headerBarDoc"
Document={this.headerBarDoc}
- DataDoc={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={DefaultStyleProvider}
- rootSelected={returnTrue}
addDocument={this.addHeaderDoc}
removeDocument={this.removeHeaderDoc}
fitContentsToBox={returnTrue}
isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
ScreenToLocalTransform={this.headerBarScreenXf}
- childHideResizeHandles={returnTrue}
+ childHideResizeHandles={true}
childDragAction="move"
dontRegisterView={true}
hideResizeHandles={true}
@@ -636,13 +637,11 @@ export class MainView extends React.Component {
<DocumentView
key="main"
Document={this.mainContainer!}
- DataDoc={undefined}
addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
- rootSelected={returnTrue}
isContentActive={returnTrue}
removeDocument={undefined}
ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
@@ -687,9 +686,9 @@ export class MainView extends React.Component {
setupMoveUpEvents(
this,
e,
- action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
- action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)),
- action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+ 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)),
false
);
};
@@ -732,13 +731,11 @@ export class MainView extends React.Component {
<div className="mainView-contentArea">
<DocumentView
Document={this._sidebarContent.proto || this._sidebarContent}
- DataDoc={undefined}
addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
- pinToPres={emptyFunction}
+ pinToPres={TabDocView.PinDoc}
docViewPath={returnEmptyDoclist}
- styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
- rootSelected={returnTrue}
+ styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
removeDocument={returnFalse}
ScreenToLocalTransform={this.mainContainerXf}
PanelWidth={this.leftMenuFlyoutWidth}
@@ -763,11 +760,9 @@ export class MainView extends React.Component {
<div key="menu" className="mainView-leftMenuPanel" style={{ background: SettingsManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
<DocumentView
Document={Doc.MyLeftSidebarMenu}
- DataDoc={undefined}
addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
- rootSelected={returnTrue}
removeDocument={returnFalse}
ScreenToLocalTransform={this.sidebarScreenToLocal}
PanelWidth={this.leftMenuWidth}
@@ -902,12 +897,10 @@ export class MainView extends React.Component {
<div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
<CollectionLinearView
Document={Doc.MyDockedBtns}
- DataDoc={undefined}
fieldKey="data"
dropAction="embed"
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
@@ -934,18 +927,16 @@ export class MainView extends React.Component {
);
}
@computed get snapLines() {
- SnappingManager.GetIsDragging();
- SnappingManager.GetIsResizing();
- const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs().lastElement();
+ const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs.lastElement();
const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.CollectionFreeFormView : undefined;
- return !dragPar?.rootDoc.freeform_snapLines ? null : (
+ 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.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
+ {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.rootDoc.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'} />
))}
</svg>
</div>
@@ -977,9 +968,6 @@ export class MainView extends React.Component {
);
}
- @computed get linkDocPreview() {
- return LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null;
- }
@observable mapBoxHackBool = false;
@computed get mapBoxHack() {
return this.mapBoxHackBool ? null : (
@@ -989,19 +977,17 @@ export class MainView extends React.Component {
select={returnFalse}
isSelected={returnFalse}
Document={this.headerBarDoc}
- DataDoc={undefined}
addDocTab={returnFalse}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={DefaultStyleProvider}
- rootSelected={returnTrue}
addDocument={returnFalse}
removeDocument={returnFalse}
fitContentsToBox={returnTrue}
isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
ScreenToLocalTransform={Transform.Identity}
- childHideResizeHandles={returnTrue}
+ childHideResizeHandles={true}
childDragAction="move"
dontRegisterView={true}
PanelWidth={this.headerBarDocWidth}
@@ -1038,6 +1024,7 @@ export class MainView extends React.Component {
{this.inkResources}
<DictationOverlay />
<SharingManager />
+ <CalendarManager />
<ServerStats />
<RTFMarkup />
<SettingsManager />
@@ -1049,8 +1036,8 @@ export class MainView extends React.Component {
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
{this._hideUI ? null : <TopBar />}
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
- {this.linkDocPreview}
+ {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
+ {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
{((page: string) => {
// prettier-ignore
@@ -1072,17 +1059,18 @@ export class MainView extends React.Component {
<RadialMenu />
<AnchorMenu />
<MapAnchorMenu />
+ <DirectionsAnchorMenu />
<DashFieldViewMenu />
<MarqueeOptionsMenu />
<TimelineMenu />
<RichTextMenu />
- <InkTranscription />
+ {/* <InkTranscription /> */}
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ <CollectionFreeFormLinksView />
<OverlayView />
- {this.mapBoxHack}
+ {/* {this.mapBoxHack} */}
<GPTPopup key="gptpopup" />
- <SchemaCSVPopUp key="schemacsvpopup" />
<GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
{/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
</div>
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 67b722e7a..af7f38937 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,8 +1,6 @@
import { isDark } from 'browndash-components';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
import { SettingsManager } from '../util/SettingsManager';
import './MainViewModal.scss';
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 20c7a08fa..285476b14 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,8 +1,8 @@
-import { action, computed, observable, ObservableMap, runInAction, trace } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, Opt } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { NumCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
@@ -15,11 +15,11 @@ import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { AnchorMenu } from './pdf/AnchorMenu';
-import React = require('react');
+import { ObservableReactComponent } from './ObservableReactComponent';
const _global = (window /* browser */ || global) /* node */ as any;
export interface MarqueeAnnotatorProps {
- rootDoc: Doc;
+ Document: Doc;
down?: number[];
scrollTop: number;
scaling?: () => number;
@@ -39,8 +39,14 @@ export interface MarqueeAnnotatorProps {
highlightDragSrcColor?: string;
}
@observer
-export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
+export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorProps> {
private _start: { x: number; y: number } = { x: 0, y: 0 };
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable private _width: number = 0;
@observable private _height: number = 0;
@computed get top() { return Math.min(this._start.y, this._start.y + this._height); } // prettier-ignore
@@ -56,12 +62,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
}
@undoBatch
- @action
makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
if (savedAnnoMap.size === 0) return undefined;
const savedAnnos = Array.from(savedAnnoMap.values())[0];
- const doc = this.props.docView().rootDoc;
+ const doc = this.props.docView().Document;
const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
const anno = savedAnnos[0];
@@ -69,8 +74,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const marqueeAnno = Docs.Create.FreeformDocument([], {
onClick: isLinkButton ? FollowLinkScript() : undefined,
backgroundColor: color,
- annotationOn: this.props.rootDoc,
- title: 'Annotation on ' + this.props.rootDoc.title,
+ annotationOn: this.props.Document,
+ title: 'Annotation on ' + this.props.Document.title,
});
marqueeAnno.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale;
marqueeAnno.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale;
@@ -82,13 +87,13 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
}
const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
- annotationOn: this.props.rootDoc,
+ annotationOn: this.props.Document,
text: this.props.selectionText(),
backgroundColor: 'transparent',
presentation_duration: 2100,
presentation_transition: 500,
presentation_zoomText: true,
- title: 'Selection on ' + this.props.rootDoc.title,
+ title: 'Selection on ' + this.props.Document.title,
});
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
@@ -126,7 +131,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@action
highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => {
// creates annotation documents for current highlights
- const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]);
+ const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
addAsAnnotation && annotationDoc && this.props.addDocument(annotationDoc);
return annotationDoc as Doc;
@@ -146,11 +151,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
const downPt = Utils.rotPt(down[0] - center.x, down[1] - center.y, NumCast(this.props.docView().screenToLocalTransform().Rotate));
const scale = this.props.docView().props.ScreenToLocalTransform().Scale;
- const scalex = this.props.mainCont.offsetWidth / NumCast(this.props.rootDoc.width);
- const scaley = this.props.mainCont.offsetHeight / NumCast(this.props.rootDoc.height);
+ const scalex = this.props.mainCont.offsetWidth / NumCast(this.props.Document.width);
+ const scaley = this.props.mainCont.offsetHeight / NumCast(this.props.Document.height);
// set marquee x and y positions to the spatially transformed position
- return { x: scalex * (downPt.x + NumCast(this.props.rootDoc.width) / scale / 2) * scale,
- y: scaley * (downPt.y + NumCast(this.props.rootDoc.height) / scale / 2) * scale + this.props.annotationLayerScrollTop }; // prettier-ignore
+ return { x: scalex * (downPt.x + NumCast(this.props.Document.width) / scale / 2) * scale,
+ y: scaley * (downPt.y + NumCast(this.props.Document.height) / scale / 2) * scale + this.props.annotationLayerScrollTop }; // prettier-ignore
};
@action
@@ -184,14 +189,14 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
- FormattedTextBox.SelectOnLoad = target[Id];
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ FormattedTextBox.SetSelectOnLoad(target);
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.rootDoc;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -213,7 +218,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
dragComplete: e => {
if (!e.aborted && e.linkDocument) {
Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
- Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().rootDoc.title;
+ Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().Document.title;
Doc.GetProto(e.linkDocument).link_displayLine = false;
}
},
@@ -242,7 +247,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
// configure and show the annotation/link menu if a the drag region is big enough
// 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.docView().rootDoc._freeform_scale, 1);
+ const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().Document._freeform_scale, 1);
['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';
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index bcbdd3ccb..5c6912121 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -156,6 +156,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps> {
<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}
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
new file mode 100644
index 000000000..9b2b00903
--- /dev/null
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -0,0 +1,21 @@
+import { action, makeObservable, observable } from 'mobx';
+import * as React from 'react';
+import './AntimodeMenu.scss';
+
+/**
+ * This is an abstract class that serves as the base for a PDF-style or Marquee-style
+ * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
+ */
+export abstract class ObservableReactComponent<T> extends React.Component<T, {}> {
+ @observable _props: React.PropsWithChildren<T>;
+ constructor(props: any) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+ componentDidUpdate(prevProps: Readonly<T>): void {
+ Object.keys(prevProps).forEach(action(pkey =>
+ (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+ ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
+ }
+}
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 9b10d1cf7..33a297fd4 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -6,6 +6,7 @@
height: 100vh;
z-index: 1001; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
/* background-color: pink; */
+ user-select: none;
}
.overlayWindow-outerDiv {
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index c7b59a8e2..915c3c18f 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,21 +1,21 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
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 { Doc } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
-import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView';
import { LightboxView } from './LightboxView';
-import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+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;
@@ -35,14 +35,14 @@ export interface OverlayWindowProps {
}
@observer
-export class OverlayWindow extends React.Component<OverlayWindowProps> {
- @observable x: number;
- @observable y: number;
- @observable width: number;
- @observable height: number;
+export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> {
+ @observable x: number = 0;
+ @observable y: number = 0;
+ @observable width: number = 0;
+ @observable height: number = 0;
constructor(props: OverlayWindowProps) {
super(props);
-
+ makeObservable(this);
const opts = props.overlayOptions;
this.x = opts.x;
this.y = opts.y;
@@ -94,8 +94,8 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
return LightboxView.LightboxDoc ? null : (
<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">
+ {this._props.overlayOptions.title || 'Untitled'}
+ <button onClick={this._props.onClick} className="overlayWindow-closeButton">
X
</button>
</div>
@@ -107,13 +107,13 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
}
@observer
-export class OverlayView extends React.Component {
+export class OverlayView extends ObservableReactComponent<{}> {
public static Instance: OverlayView;
- @observable.shallow
- private _elements: JSX.Element[] = [];
+ @observable.shallow _elements: JSX.Element[] = [];
constructor(props: any) {
super(props);
+ makeObservable(this);
if (!OverlayView.Instance) {
OverlayView.Instance = this;
new _global.ResizeObserver(
@@ -135,11 +135,7 @@ export class OverlayView extends React.Component {
@action
addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
- const remove = action(() => {
- const index = this._elements.indexOf(ele);
- if (index !== -1) this._elements.splice(index, 1);
- });
- ele = (
+ const div = (
<div
key={Utils.GenerateGuid()}
className="overlayView-wrapperDiv"
@@ -153,8 +149,11 @@ export class OverlayView extends React.Component {
{ele}
</div>
);
- this._elements.push(ele);
- return remove;
+ this._elements.push(div);
+ return action(() => {
+ const index = this._elements.indexOf(div);
+ if (index !== -1) this._elements.splice(index, 1);
+ });
}
@action
@@ -222,7 +221,6 @@ export class OverlayView extends React.Component {
style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
<DocumentView
Document={d}
- rootSelected={returnTrue}
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={this.removeOverlayDoc}
@@ -256,7 +254,6 @@ export class OverlayView extends React.Component {
return (
<div className="overlayView" id="overlayView">
<div>{this._elements}</div>
- <CollectionFreeFormLinksView key="freeformLinks" />
{this.overlayDocs}
</div>
);
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
deleted file mode 100644
index 749eb08a2..000000000
--- a/src/client/views/Palette.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { IReactionDisposer, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils';
-import { Transform } from '../util/Transform';
-import { DocumentView } from './nodes/DocumentView';
-import './Palette.scss';
-
-export interface PaletteProps {
- x: number;
- y: number;
- thumb: number[];
- thumbDoc: Doc;
-}
-
-@observer
-export default class Palette extends React.Component<PaletteProps> {
- private _selectedDisposer?: IReactionDisposer;
- @observable private _selectedIndex: number = 0;
-
- componentDidMount = () => {
- this._selectedDisposer = reaction(
- () => NumCast(this.props.thumbDoc.selectedIndex),
- i => (this._selectedIndex = i),
- { fireImmediately: true }
- );
- };
-
- componentWillUnmount = () => {
- this._selectedDisposer?.();
- };
-
- render() {
- return (
- <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
- <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
- <div className="palette-thumbContent" style={{ transform: `translate(-${this._selectedIndex * 50 + 10}px, 0px)` }}>
- <DocumentView
- Document={this.props.thumbDoc}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={returnFalse}
- rootSelected={returnTrue}
- pinToPres={emptyFunction}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- isDocumentActive={returnTrue}
- isContentActive={emptyFunction}
- focus={emptyFunction}
- docViewPath={returnEmptyDoclist}
- styleProvider={returnEmptyString}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index e3a43d45f..5ec9a7d46 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,41 +1,50 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, Opt } from '../../fields/Doc';
import { lightOrDark, returnFalse } from '../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { Doc, Opt } from '../../fields/Doc';
+import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
import { ImageUtils } from '../util/Import & Export/ImageUtils';
import { Transform } from '../util/Transform';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { UndoManager, undoBatch } from '../util/UndoManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
import './PreviewCursor.scss';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
-export class PreviewCursor extends React.Component<{}> {
- static _onKeyPress?: (e: KeyboardEvent) => void;
- static _getTransform: () => Transform;
- static _addDocument: (doc: Doc | Doc[]) => boolean;
- static _addLiveTextDoc: (doc: Doc) => void;
- static _nudge?: undefined | ((x: number, y: number) => boolean);
- static _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
- @observable static _clickPoint = [0, 0];
- @observable public static Visible = false;
- public static Doc: Opt<Doc>;
+export class PreviewCursor extends ObservableReactComponent<{}> {
+ static _instance: PreviewCursor;
+ public static get Instance() {
+ return PreviewCursor._instance;
+ }
+
+ _onKeyPress?: (e: KeyboardEvent) => void;
+ _getTransform?: () => Transform;
+ _addDocument?: (doc: Doc | Doc[]) => boolean;
+ _addLiveTextDoc?: (doc: Doc) => void;
+ _nudge?: undefined | ((x: number, y: number) => boolean);
+ _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
+ @observable _clickPoint: number[];
+ @observable public Visible = false;
+ public Doc: Opt<Doc>;
constructor(props: any) {
super(props);
+ makeObservable(this);
+ PreviewCursor._instance = this;
+ this._clickPoint = observable([0, 0]);
document.addEventListener('keydown', this.onKeyPress);
document.addEventListener('paste', this.paste, true);
}
paste = async (e: ClipboardEvent) => {
- if (PreviewCursor.Visible && e.clipboardData) {
- const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
- runInAction(() => (PreviewCursor.Visible = false));
+ if (this.Visible && e.clipboardData) {
+ const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
+ runInAction(() => (this.Visible = false));
// tests for URL and makes web document
const re: any = /^https?:\/\//g;
const plain = e.clipboardData.getData('text/plain');
- if (plain) {
+ if (plain && newPoint) {
// tests for youtube and makes video document
if (plain.indexOf('www.youtube.com/watch') !== -1) {
const batch = UndoManager.StartBatch('youtube upload');
@@ -47,21 +56,22 @@ export class PreviewCursor extends React.Component<{}> {
x: newPoint[0],
y: newPoint[1],
};
- PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end);
+ this._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, this._addDocument ?? returnFalse).then(batch.end);
} else if (re.test(plain)) {
const url = plain;
- if (url.startsWith(window.location.href)) {
- undoBatch(() =>
- PreviewCursor._addDocument(
- Docs.Create.WebDocument(url, {
- title: url,
- _width: 500,
- _height: 300,
- data_useCors: true,
- x: newPoint[0],
- y: newPoint[1],
- })
- )
+ if (!url.startsWith(window.location.href)) {
+ undoBatch(
+ () =>
+ this._addDocument?.(
+ Docs.Create.WebDocument(url, {
+ title: url,
+ _width: 500,
+ _height: 300,
+ data_useCors: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
)();
} else alert('cannot paste dash into itself');
} else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
@@ -70,13 +80,13 @@ export class PreviewCursor extends React.Component<{}> {
const strs = docids[0].split(','); // hack! docids[0] is the top left of the selection rectangle
const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length));
const pty = Number(strs[1].substring(0, strs[1].length - 1));
- Doc.Paste(docids.slice(1), clone, PreviewCursor._addDocument, ptx, pty, newPoint);
+ this._addDocument && Doc.Paste(docids.slice(1), clone, this._addDocument, ptx, pty, newPoint);
e.stopPropagation();
} else {
FormattedTextBox.PasteOnLoad = e;
if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
- UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+ UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
}
}
//pasting in images
@@ -84,17 +94,19 @@ export class PreviewCursor extends React.Component<{}> {
const re: any = /<img src="(.*?)"/g;
const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
- undoBatch(() => {
- const doc = Docs.Create.ImageDocument(arr[1], {
- _width: 300,
- title: arr[1],
- x: newPoint[0],
- y: newPoint[1],
- });
- ImageUtils.ExtractExif(doc);
- PreviewCursor._addDocument(doc);
- })();
- } else if (e.clipboardData.items.length) {
+ if (newPoint) {
+ undoBatch(() => {
+ const doc = Docs.Create.ImageDocument(arr[1], {
+ _width: 300,
+ title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ });
+ ImageUtils.ExtractExif(doc);
+ this._addDocument?.(doc);
+ })();
+ }
+ } else if (e.clipboardData.items.length && newPoint) {
const batch = UndoManager.StartBatch('collection view drop');
const files: File[] = [];
Array.from(e.clipboardData.items).forEach(item => {
@@ -102,7 +114,7 @@ export class PreviewCursor extends React.Component<{}> {
file && files.push(file);
});
const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
- generatedDocuments.forEach(PreviewCursor._addDocument);
+ this._addDocument && generatedDocuments.forEach(this._addDocument);
batch.end();
}
}
@@ -135,25 +147,25 @@ export class PreviewCursor extends React.Component<{}> {
) {
if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
- PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
- ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (PreviewCursor.Visible = false);
+ this.Visible && this._onKeyPress?.(e);
+ ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
}
- } else if (PreviewCursor.Visible) {
+ } else if (this.Visible) {
if (e.key === 'ArrowRight') {
- PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ this._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
} else if (e.key === 'ArrowLeft') {
- PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ this._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
} else if (e.key === 'ArrowUp') {
- PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ this._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
} else if (e.key === 'ArrowDown') {
- PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ this._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
}
}
};
//when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
- PreviewCursor.Visible = false;
+ this.Visible = false;
};
@action
@@ -167,23 +179,21 @@ export class PreviewCursor extends React.Component<{}> {
nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean),
slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>
) {
- this._clickPoint = [x, y];
- this._onKeyPress = onKeyPress;
- this._addLiveTextDoc = addLiveText;
- this._getTransform = getTransform;
- this._addDocument = addDocument || returnFalse;
- this._nudge = nudge;
- this._slowLoadDocuments = slowLoadDocuments;
- this.Visible = true;
+ const self = PreviewCursor.Instance;
+ if (self) {
+ self._clickPoint = [x, y];
+ self._onKeyPress = onKeyPress;
+ self._addLiveTextDoc = addLiveText;
+ self._getTransform = getTransform;
+ self._addDocument = addDocument || returnFalse;
+ self._nudge = nudge;
+ self._slowLoadDocuments = slowLoadDocuments;
+ self.Visible = true;
+ }
}
render() {
- return !PreviewCursor._clickPoint || !PreviewCursor.Visible ? null : (
- <div
- className="previewCursor"
- onBlur={this.onBlur}
- tabIndex={0}
- ref={e => e?.focus()}
- style={{ color: lightOrDark(PreviewCursor.Doc?.backgroundColor ?? 'white'), transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
+ return !this._clickPoint || !this.Visible ? null : (
+ <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.scss b/src/client/views/PropertiesButtons.scss
index b801b3abf..b8c73b6d3 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -1,6 +1,6 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
-$linkGap : 3px;
+$linkGap: 3px;
.propertiesButtons-linkFlyout {
grid-column: 2/4;
@@ -23,7 +23,7 @@ $linkGap : 3px;
// margin-right: 7px;
// margin-left: 8px;
height: 28px;
- // width: 226px;//29px;
+ // width: 226px;//29px;
display: flex;
align-items: center;
// height: 25px;
@@ -39,10 +39,9 @@ $linkGap : 3px;
// font-size: 75%;
transition: transform 0.2s;
// text-align: center;
-
// justify-content: center;
-
+
// margin-right: 10px;
// margin-left: 4px;
@@ -55,34 +54,34 @@ $linkGap : 3px;
.propertiesButtons-linkButton-empty.toggle-on {
background-color: $medium-blue;
color: $white;
- width:100%
+ width: 100%;
}
.propertiesButtons-linkButton-empty.toggle-hover {
background-color: $light-blue;
color: $black;
- width:100%
+ width: 100%;
}
.propertiesButtons-linkButton-empty.toggle-off {
- background-color: white;//$dark-gray;
+ background-color: white; //$dark-gray;
color: black; //white;
- width:100%
+ width: 100%;
}
.propertiesButtons-icon {
- margin-left:8px;
+ margin-left: 8px;
}
.propertiesButtons {
- position:relative;
+ position: relative;
width: 100%;
// margin-top: 3px;
-// // grid-column: 1/4;
-// width: 100%;
-// height: auto;
-// display: flex;
-// // flex-direction: row;
-// // flex-wrap: wrap;
-// padding-bottom: 5.5px;
+ // // grid-column: 1/4;
+ // width: 100%;
+ // height: auto;
+ // display: flex;
+ // // flex-direction: row;
+ // // flex-wrap: wrap;
+ // padding-bottom: 5.5px;
}
.onClickFlyout-editScript {
@@ -95,11 +94,10 @@ $linkGap : 3px;
padding: 4px;
}
-
.propertiesButtons-button {
pointer-events: auto;
- padding-right: 8px;//5px;
- width: 100%;//width: 25px;
+ padding-right: 8px; //5px;
+ width: 100%; //width: 25px;
border-radius: 5px;
margin-right: 20px;
margin-bottom: 8px;
@@ -161,12 +159,11 @@ $linkGap : 3px;
margin-left: 4px;
&:hover {
- filter:brightness(0.85);
+ filter: brightness(0.85);
cursor: pointer;
}
}
-
@-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
@@ -194,4 +191,4 @@ $linkGap : 3px;
100% {
box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 18f53b8e7..8540e81e1 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { DocumentView, OpenWhere } from './nodes/DocumentView';
import './PropertiesButtons.scss';
-import React = require('react');
+import * as React from 'react';
enum UtilityButtonState {
Default,
@@ -40,14 +40,17 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@observable public static Instance: PropertiesButtons;
@computed get selectedDoc() {
- return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc;
+ return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.Document;
+ }
+ @computed get selectedLayoutDoc() {
+ return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.layoutDoc;
}
@computed get selectedTabView() {
- return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost;
+ 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.selectedDoc;
+ 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
@@ -61,8 +64,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
fillWidth={true}
toggleType={ToggleType.BUTTON}
onClick={undoable(() => {
- if (SelectionManager.Views().length > 1) {
- SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
+ if (SelectionManager.Views.length > 1) {
+ SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
} else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
}, property)}
/>
@@ -79,8 +82,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => `${on ? 'Set' : 'Remove'} lightbox flag`,
on => 'window-restore',
onClick => {
- SelectionManager.Views().forEach(dv => {
- const containerDoc = dv.rootDoc;
+ SelectionManager.Views.forEach(dv => {
+ const containerDoc = dv.Document;
//containerDoc.followAllLinks =
// containerDoc.noShadow =
// containerDoc.layout_disableBrushing =
@@ -88,8 +91,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
//containerDoc._freeform_fitContentsToBox =
containerDoc._isLightbox = !containerDoc._isLightbox;
//containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
- const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
+ //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
});
}
@@ -103,7 +106,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => 'Switch between title styles',
on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ? <MdSubtitles/> :) , //,'text-width', on ? <MdSubtitles/> : <MdSubtitlesOff/>,
(dv, doc) => {
- const tdoc = dv?.rootDoc || doc;
+ const tdoc = dv?.Document || doc;
const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
tdoc._layout_showTitle = newtitle ? newtitle : undefined;
}
@@ -197,8 +200,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// on => `${on ? 'Set' : 'Remove'} lightbox flag`,
// on => 'window-restore',
// onClick => {
- // SelectionManager.Views().forEach(dv => {
- // const containerDoc = dv.rootDoc;
+ // SelectionManager.Views.forEach(dv => {
+ // const containerDoc = dv.Document;
// //containerDoc.followAllLinks =
// // containerDoc.noShadow =
// // containerDoc.disableDocBrushing =
@@ -207,7 +210,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// containerDoc._isLightbox = !containerDoc._isLightbox;
// //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
// const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- // //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ // //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
// containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
// });
// }
@@ -233,7 +236,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
'_layout_showCaption',
on => `${on ? 'Hide' : 'Show'} caption footer`,
on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
- (dv, doc) => ((dv?.rootDoc || doc)._layout_showCaption = (dv?.rootDoc || doc)._layout_showCaption === undefined ? 'caption' : undefined)
+ (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined)
);
}
@@ -244,7 +247,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
'_chromeHidden',
on => `${on ? 'Show' : 'Hide'} editing UI`,
on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
- (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden)
+ (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden)
);
}
@@ -349,8 +352,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
handlePerspectiveChange = (e: any) => {
this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
- SelectionManager.Views()
- .filter(dv => dv.docView)
+ SelectionManager.Views.filter(dv => dv.docView)
.map(dv => dv.docView!)
.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
};
@@ -413,8 +415,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
@action
handleOptionChange = (onClick: string) => {
- SelectionManager.Views()
- .filter(dv => dv.docView)
+ SelectionManager.Views.filter(dv => dv.docView)
.map(dv => dv.docView!)
.forEach(docView => {
const linkButton = IsFollowLinkScript(docView.props.Document.onClick);
@@ -440,7 +441,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
editOnClickScript = () => {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, 'onClick'));
+ if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
};
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index b1e99c3f5..58d3b72e8 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -34,10 +34,10 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
});
render() {
- return !SelectionManager.Views().length ? null : (
+ return !SelectionManager.Views.length ? null : (
<div className="propertiesDocBacklinksSelector" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
{this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
- <LinkMenu docView={SelectionManager.Views().lastElement()} clearLinkEditor={undefined} itemHandler={this.getOnClick} style={{ left: 0, top: 0 }} />
+ <LinkMenu docView={SelectionManager.Views.lastElement()} clearLinkEditor={undefined} itemHandler={this.getOnClick} style={{ left: 0, top: 0 }} />
</div>
);
}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index d157e7b1c..a710e7816 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,13 +1,14 @@
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { Cast, StrCast } from '../../fields/Types';
import { DocFocusOrOpen } from '../util/DocumentManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import './PropertiesDocContextSelector.scss';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { DocumentView, OpenWhere } from './nodes/DocumentView';
-import './PropertiesDocContextSelector.scss';
type PropertiesDocContextSelectorProps = {
DocView?: DocumentView;
@@ -17,11 +18,16 @@ type PropertiesDocContextSelectorProps = {
};
@observer
-export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
+export class PropertiesDocContextSelector extends ObservableReactComponent<PropertiesDocContextSelectorProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get _docs() {
- if (!this.props.DocView) return [];
- const target = this.props.DocView.props.Document;
- const targetContext = this.props.DocView.props.docViewPath().lastElement()?.rootDoc;
+ if (!this._props.DocView) return [];
+ const target = this._props.DocView._props.Document;
+ const targetContext = this._props.DocView._props.docViewPath().lastElement()?.Document;
const embeddings = DocListCast(target.proto_embeddings);
const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
@@ -40,23 +46,23 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
);
return doclayouts
- .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
+ .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.Document))
.filter(doc => !Doc.IsSystem(doc))
.filter(doc => doc !== targetContext)
.map(doc => ({ col: doc, target }));
}
getOnClick = (col: Doc, target: Doc) => {
- if (!this.props.DocView) return;
+ if (!this._props.DocView) return;
col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col;
- DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col);
+ DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col);
};
render() {
if (this._docs.length < 1) return undefined;
return (
<div>
- {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
+ {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>
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
index 321b6300c..3f92a70f8 100644
--- a/src/client/views/PropertiesSection.scss
+++ b/src/client/views/PropertiesSection.scss
@@ -1,11 +1,10 @@
-@import './global/globalCssVariables.scss';
+@import './global/globalCssVariables.module.scss';
.propertiesView-section {
-
.propertiesView-content {
padding: 10px;
}
-
+
.propertiesView-sectionTitle {
text-align: center;
display: flex;
@@ -14,7 +13,7 @@
font-weight: bold;
justify-content: space-between;
align-items: center;
-
+
.propertiesView-sectionTitle-icon {
width: 20px;
height: 20px;
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index bd586b2f9..2b9bfb505 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 2da2ec568..8581bdf73 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables.scss';
+@import './global/globalCssVariables.module.scss';
.propertiesView-presentationTrails-title {
display: flex;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1b9b26111..2fcb5d12a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,14 +1,15 @@
-import React = require('react');
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Checkbox, Tooltip } from '@material-ui/core';
+import { Checkbox, Tooltip } from '@mui/material';
import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { ColorState, SketchPicker } from 'react-color';
+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, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -16,8 +17,7 @@ import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
import { ComputedField } from '../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
@@ -26,19 +26,20 @@ import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
import { EditableView } from './EditableView';
import { FilterPanel } from './FilterPanel';
import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+import { ObservableReactComponent } from './ObservableReactComponent';
import { PropertiesButtons } from './PropertiesButtons';
import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -49,12 +50,13 @@ interface PropertiesViewProps {
}
@observer
-export class PropertiesView extends React.Component<PropertiesViewProps> {
+export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
public static Instance: PropertiesView | undefined;
constructor(props: any) {
super(props);
+ makeObservable(this);
PropertiesView.Instance = this;
}
@@ -63,11 +65,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get selectedDoc() {
- return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
+ return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
+ }
+
+ @computed get selectedLayoutDoc() {
+ return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
}
@computed get selectedDocumentView() {
- if (SelectionManager.Views().length) return SelectionManager.Views()[0];
- if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+ if (SelectionManager.Views.length) return SelectionManager.Views[0];
+ if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.Document);
return undefined;
}
@computed get isPres(): boolean {
@@ -126,24 +132,24 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
}
- rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(NumCast(this.selectedDoc?._width), this.props.width - 20));
- rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedDoc?._width) ? Math.min(NumCast(this.selectedDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
+ rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
+ rtfHeight = () => (!this.selectedLayoutDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedLayoutDoc?._width) ? Math.min(NumCast(this.selectedLayoutDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
@action
docWidth = () => {
- if (this.selectedDoc) {
- const layoutDoc = this.selectedDoc;
+ const layoutDoc = this.selectedLayoutDoc;
+ if (layoutDoc) {
const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
- if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
- return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
+ if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this._props.width - 20));
+ return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this._props.width - 20) : this._props.width - 20;
}
return 0;
};
@action
docHeight = () => {
- if (this.selectedDoc && this.dataDoc) {
- const layoutDoc = this.selectedDoc;
+ const layoutDoc = this.selectedLayoutDoc;
+ if (layoutDoc && this.dataDoc) {
return Math.max(
70,
Math.min(
@@ -151,10 +157,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
Doc.NativeAspect(layoutDoc, undefined, true)
? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true)
: layoutDoc._layout_fitWidth
- ? !Doc.NativeHeight(this.dataDoc)
- ? NumCast(this.props.height)
- : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
- : NumCast(layoutDoc._height) || 50
+ ? !Doc.NativeHeight(this.dataDoc)
+ ? NumCast(this._props.height)
+ : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this._props.height))
+ : NumCast(layoutDoc._height) || 50
)
);
}
@@ -165,7 +171,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const rows: JSX.Element[] = [];
if (this.dataDoc && this.selectedDoc) {
const ids = new Set<string>(reqdKeys);
- const docs: Doc[] = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
+ const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && ids.add(key)));
// prettier-ignore
@@ -214,7 +220,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
setKeyValue = (value: string) => {
- const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc!] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
+ const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc!] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc => {
if (value.indexOf(':') !== -1) {
const newVal = value[0].toUpperCase() + value.substring(1, value.length);
@@ -250,12 +256,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@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={true} addDocTab={this._props.addDocTab} />;
}
@computed get contextCount() {
if (this.selectedDocumentView) {
- const target = this.selectedDocumentView.props.Document;
+ const target = this.selectedDocumentView.Document;
const embeddings = DocListCast(target.proto_embeddings);
return embeddings.length - 1;
} else {
@@ -265,7 +271,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get links() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
- return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
+ return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
}
@computed get linkCount() {
@@ -278,7 +284,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get layoutPreview() {
- if (SelectionManager.Views().length > 1) {
+ if (SelectionManager.Views.length > 1) {
return '-- multiple selected --';
}
if (this.selectedDoc) {
@@ -288,11 +294,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<div ref={this.propertiesDocViewRef} style={{ pointerEvents: 'none', display: 'inline-block', height: panelHeight() }} key={this.selectedDoc[Id]}>
<DocumentView
- Document={layoutDoc}
- DataDoc={this.dataDoc}
+ Document={this.selectedDoc}
+ TemplateDataDocument={Doc.AreProtosEqual(this.dataDoc, this.selectedDoc) ? undefined : this.dataDoc}
renderDepth={1}
fitContentsToBox={returnTrue}
- rootSelected={returnFalse}
styleProvider={DefaultStyleProvider}
docViewPath={returnEmptyDoclist}
dontCenter={'y'}
@@ -328,7 +333,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@undoBatch
changePermissions = (e: any, user: string) => {
- const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData]));
+ const docs = SelectionManager.Views.length < 2 ? [this.selectedDoc] : SelectionManager.Views.map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
};
@@ -374,7 +379,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
color={SettingsManager.userColor}
onClick={action(() => {
if (this.selectedDocumentView || this.selectedDoc) {
- SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
+ SharingManager.Instance.open(this.selectedDocumentView?.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
}
})}
/>
@@ -446,14 +451,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@computed get sharingTable() {
// all selected docs
- const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+ 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 individualTableEntries = [];
const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
- const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc);
+ const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData];
// adds each user to usersAdded
SharingManager.Instance.users.forEach(eachUser => {
var userOnDoc = true;
@@ -562,7 +567,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get editableTitle() {
const titles = new Set<string>();
- SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
+ SelectionManager.Views.forEach(dv => titles.add(StrCast(dv.Document.title)));
const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
return (
<div>
@@ -616,10 +621,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@undoBatch
- @action
setTitle = (value: string | number) => {
- if (SelectionManager.Views().length > 1) {
- SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true));
+ if (SelectionManager.Views.length > 1) {
+ SelectionManager.Views.map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
} else if (this.dataDoc) {
if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true);
else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true);
@@ -627,13 +631,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@undoBatch
- @action
rotate = (angle: number) => {
const _centerPoints: { X: number; Y: number }[] = [];
- if (this.selectedDoc) {
- const doc = this.selectedDoc;
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- const ink = Cast(doc.data, InkField)?.inkData;
+ const doc = this.selectedDoc;
+ const layout = this.selectedLayoutDoc;
+ if (doc && layout) {
+ if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
+ const ink = Cast(doc.stroke, InkField)?.inkData;
if (ink) {
const xs = ink.map(p => p.X);
const ys = ink.map(p => p.Y);
@@ -646,9 +650,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
var index = 0;
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- doc.rotation = NumCast(doc.rotation) + angle;
- const inks = Cast(doc.data, InkField)?.inkData;
+ 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;
if (inks) {
const newPoints: { X: number; Y: number }[] = [];
inks.forEach(ink => {
@@ -656,7 +660,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
newPoints.push({ X: newX, Y: newY });
});
- doc.data = new InkField(newPoints);
+ doc.stroke = new InkField(newPoints);
const xs = newPoints.map(p => p.X);
const ys = newPoints.map(p => p.Y);
const left = Math.min(...xs);
@@ -664,8 +668,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- doc._height = bottom - top;
- doc._width = right - left;
+ layout._height = bottom - top;
+ layout._width = right - left;
}
index++;
}
@@ -871,7 +875,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<SketchPicker
onChange={undoable(
- action((color: ColorState) => setter(color.hex)),
+ action((color: ColorResult) => setter(color.hex)),
'set stroke color property'
)}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
@@ -1053,7 +1057,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@undoBatch
- @action
changeDash = () => {
this.dashdStk = this.dashdStk === '2' ? '0' : '2';
};
@@ -1168,7 +1171,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<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 rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
+ <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
</PropertiesSection>
);
@@ -1178,7 +1181,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<>
<PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
- {this.selectedDoc?.layout_isSvg ? this.appearanceEditor : null}
+ {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null}
</PropertiesSection>
<PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
{this.transformEditor}
@@ -1672,8 +1675,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
if (!this.selectedDoc && !this.isPres) {
return (
- <div className="propertiesView" style={{ width: this.props.width }}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
+ <div className="propertiesView" style={{ width: this._props.width }}>
+ <div className="propertiesView-title" style={{ width: this._props.width }}>
No Document Selected
</div>
</div>
@@ -1686,11 +1689,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
style={{
background: SettingsManager.userBackgroundColor,
color: SettingsManager.userColor,
- width: this.props.width,
- minWidth: this.props.width,
+ width: this._props.width,
+ minWidth: this._props.width,
}}>
<div className="propertiesView-propAndInfoGrouping">
- <div className="propertiesView-title" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" 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} />
@@ -1718,8 +1721,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
? (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 }}>
+ <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 }}>
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 416162aeb..086f40e96 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../fields/Doc';
@@ -27,6 +27,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
constructor(props: ScriptBoxProps) {
super(props);
+ makeObservable(this);
this._scriptText = props.initialText || '';
}
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 9966dbced..acf0ecff4 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
@@ -7,31 +7,39 @@ import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SettingsManager } from '../util/SettingsManager';
import { undoable } from '../util/UndoManager';
-import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { ObservableReactComponent } from './ObservableReactComponent';
import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
-@observer
-export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void; value: { [key: string]: any }; name?: string }> {
+interface ReplProps {
+ scrollToBottom: () => void;
+ value: { [key: string]: any };
+ name?: string;
+}
+export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps> {
@observable collapsed = true;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@action
toggle = () => {
this.collapsed = !this.collapsed;
- this.props.scrollToBottom();
+ this._props.scrollToBottom();
};
render() {
- const val = this.props.value;
+ const val = this._props.value;
const proto = Object.getPrototypeOf(val);
const name = (proto && proto.constructor && proto.constructor.name) || String(val);
- const title = this.props.name ? (
+ const title = (
<>
- <b>{this.props.name} : </b>
+ {this.props.name ? <b>{this._props.name} : </b> : <></>}
{name}
</>
- ) : (
- name
);
if (this.collapsed) {
return (
@@ -53,7 +61,7 @@ export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: ()
</div>
<div className="scriptingObject-fields">
{Object.keys(val).map(key => (
- <ScriptingValueDisplay {...this.props} name={key} />
+ <ScriptingValueDisplay {...this._props} name={key} />
))}
</div>
</div>
@@ -62,40 +70,43 @@ export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: ()
}
}
+interface replValueProps {
+ scrollToBottom: () => void;
+ value: any;
+ name?: string;
+}
@observer
-export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: () => void; value: any; name?: string }> {
+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 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') {
- return <ScriptingObjectDisplay scrollToBottom={this.props.scrollToBottom} value={val} name={this.props.name} />;
+ return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
} else if (typeof val === 'function') {
const name = '[Function]';
- const title = this.props.name ? (
- <>
- <b>{this.props.name} : </b>
- {name}
- </>
- ) : (
- name
- );
- return <div className="scriptingObject-leaf">{title}</div>;
- } else {
- const name = String(val);
- const title = this.props.name ? (
- <>
- <b>{this.props.name} : </b>
- {name}
- </>
- ) : (
- name
- );
- return <div className="scriptingObject-leaf">{title}</div>;
+ return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
}
+ return <div className="scriptingObject-leaf">{title(String(val))}</div>;
}
}
@observer
-export class ScriptingRepl extends React.Component {
+export class ScriptingRepl extends ObservableReactComponent<{}> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable private commands: { command: string; result: any }[] = [];
private commandsHistory: string[] = [];
@@ -136,7 +147,8 @@ export class ScriptingRepl extends React.Component {
const m = parseInt(match[1]);
usedDocuments.push(m);
} else {
- return ts.createPropertyAccess(ts.createIdentifier('args'), node);
+ return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+ // ts.createPropertyAccess(ts.createIdentifier('args'), node);
}
}
}
@@ -156,7 +168,7 @@ export class ScriptingRepl extends React.Component {
case 'Enter': {
e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.props.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) {
@@ -228,7 +240,8 @@ export class ScriptingRepl extends React.Component {
ele && ele.scroll({ behavior: 'auto', top: ele.scrollHeight });
}
- componentDidUpdate() {
+ componentDidUpdate(prevProps: Readonly<{}>) {
+ super.componentDidUpdate(prevProps);
if (this.shouldScroll) {
this.shouldScroll = false;
this.scrollToBottom();
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1e1b8e0e6..0f4a4260c 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,27 +1,28 @@
-import { computed } from 'mobx';
+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 { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { DocCast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocUtils, Docs } from '../documents/Documents';
import { LinkManager } from '../util/LinkManager';
import { SearchUtil } from '../util/SearchUtil';
import { Transform } from '../util/Transform';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import './SidebarAnnos.scss';
+import { StyleProp } from './StyleProvider';
import { CollectionStackingView } from './collections/CollectionStackingView';
import { FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import './SidebarAnnos.scss';
-import { StyleProp } from './StyleProvider';
-import React = require('react');
interface ExtraProps {
fieldKey: string;
+ Document: Doc;
layoutDoc: Doc;
- rootDoc: Doc;
dataDoc: Doc;
// usePanelWidth: boolean;
showSidebar: boolean;
@@ -34,15 +35,16 @@ interface ExtraProps {
moveDocument: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string) => boolean;
}
@observer
-export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
- constructor(props: Readonly<FieldViewProps & ExtraProps>) {
+export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & ExtraProps> {
+ constructor(props: any) {
super(props);
- // this.props.dataDoc[this.sidebarKey] = new List<Doc>(); // bcz: can't do this here. it blows away existing things and isn't a robust solution for making sure the field exists -- instead this should happen when the document is created and/or shared
+ makeObservable(this);
}
+
_stackRef = React.createRef<CollectionStackingView>();
@computed get allMetadata() {
const keys = new Map<string, FieldResult<Field>>();
- DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc =>
+ DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
SearchUtil.documentKeys(doc)
.filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
.map(key => keys.set(key, doc[key]))
@@ -51,7 +53,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
}
@computed get allHashtags() {
const keys = new Set<string>();
- DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
+ DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
return Array.from(keys.keys())
.filter(key => key[0])
.filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase()))
@@ -59,7 +61,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
}
@computed get allUsers() {
const keys = new Set<string>();
- DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
+ DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
return Array.from(keys.keys()).sort();
}
@@ -69,7 +71,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
.join(' ');
const target = Docs.Create.TextDocument(startup, {
title: '-note-',
- annotationOn: this.props.rootDoc,
+ annotationOn: this._props.Document,
_width: 200,
_height: 50,
_layout_fitWidth: true,
@@ -77,7 +79,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
_text_fontSize: StrCast(Doc.UserDoc().fontSize),
_text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
});
- FormattedTextBox.SelectOnLoad = target[Id];
+ FormattedTextBox.SetSelectOnLoad(target);
FormattedTextBox.DontSelectInitialText = true;
const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
link && (link.link_displayLine = false);
@@ -147,10 +149,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
return target;
};
makeDocUnfiltered = (doc: Doc) => {
- if (DocListCast(this.props.rootDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ if (DocListCast(this._props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
if (this.childFilters()) {
// if any child filters exist, get rid of them
- this.props.layoutDoc._childFilters = new List<string>();
+ this._props.layoutDoc._childFilters = new List<string>();
}
return true;
}
@@ -158,27 +160,27 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
};
get sidebarKey() {
- return this.props.fieldKey + '_sidebar';
+ return this._props.fieldKey + '_sidebar';
}
filtersHeight = () => 38;
screenToLocalTransform = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
- .translate(Doc.NativeWidth(this.props.dataDoc), 0)
- .scale(this.props.NativeDimScaling?.() || 1);
+ .translate(Doc.NativeWidth(this._props.dataDoc), 0)
+ .scale(this._props.NativeDimScaling?.() || 1);
panelWidth = () =>
- !this.props.showSidebar
+ !this._props.showSidebar
? 0
- : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any)
- ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
- : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
- panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
- addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
- removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
- childFilters = () => StrListCast(this.props.layoutDoc._childFilters);
+ : this._props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this._props.layoutDoc.type as any)
+ ? this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1)
+ : ((NumCast(this._props.nativeWidth) - Doc.NativeWidth(this._props.dataDoc)) * this._props.PanelWidth()) / NumCast(this._props.nativeWidth);
+ panelHeight = () => this._props.PanelHeight() - this.filtersHeight();
+ addDocument = (doc: Doc | Doc[]) => this._props.sidebarAddDocument(doc, this.sidebarKey);
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
+ removeDocument = (doc: Doc | Doc[]) => this._props.removeDocument(doc, this.sidebarKey);
+ childFilters = () => StrListCast(this._props.layoutDoc._childFilters);
layout_showTitle = () => 'title';
- setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
+ setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight());
sortByLinkAnchorY = (a: Doc, b: Doc) => {
const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].link_anchor_1).y;
const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].link_anchor_1).y;
@@ -188,7 +190,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
const renderTag = (tag: string) => {
const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
return (
- <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
{tag}
</div>
);
@@ -196,7 +198,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
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.rootDoc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
{tag}
</div>
);
@@ -204,20 +206,20 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
const renderUsers = (user: string) => {
const active = this.childFilters().includes(`author:${user}:check`);
return (
- <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, undefined, e.shiftKey)}>
+ <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}>
{user}
</div>
);
};
// TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
- return !this.props.showSidebar ? null : (
+ return !this._props.showSidebar ? null : (
<div
style={{
position: 'absolute',
- pointerEvents: this.props.isContentActive() ? 'all' : undefined,
- top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._layout_showTitle) === 'title' ? 15 : 0,
+ pointerEvents: this._props.isContentActive() ? 'all' : undefined,
+ top: this._props.Document.type !== DocumentType.RTF && StrCast(this._props.Document._layout_showTitle) === 'title' ? 15 : 0,
right: 0,
- background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
+ background: this._props.styleProvider?.(this._props.Document, this._props, StyleProp.WidgetColor),
width: `100%`,
height: '100%',
}}>
@@ -231,7 +233,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
<div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
<CollectionStackingView
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -246,14 +248,14 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
NativeDimScaling={returnOne}
//childlayout_showTitle={this.layout_showTitle}
isAnyChildContentActive={returnFalse}
- childDocumentsActive={this.props.isContentActive}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- childHideDecorationTitle={returnTrue}
+ childDocumentsActive={this._props.isContentActive}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ childHideDecorationTitle={true}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
ScreenToLocalTransform={this.screenToLocalTransform}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
type_collection={CollectionViewType.Stacking}
fieldKey={this.sidebarKey}
pointerEvents={returnAll}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2162d8878..3e7e4be90 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,6 +1,6 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { Tooltip } from '@mui/material';
import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from 'browndash-components';
import { action, runInAction, untracked } from 'mobx';
import { extname } from 'path';
@@ -23,7 +23,7 @@ import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PropertiesView } from './PropertiesView';
import './StyleProvider.scss';
-import React = require('react');
+import * as React from 'react';
export enum StyleProp {
TreeViewIcon = 'treeView_Icon',
@@ -64,6 +64,9 @@ function toggleLockedPosition(doc: Doc) {
export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
}
+export function testFieldProps(toBeDetermined: any): toBeDetermined is FieldViewProps {
+ return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
+}
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 ${
@@ -77,19 +80,20 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
// a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
//
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any {
const remoteDocHeader = 'author;author_date;noMargin';
const docProps = testDocProps(props) ? props : undefined;
+ const fieldProps = testFieldProps(props) ? props : undefined;
const selected = property.includes(':selected');
const isCaption = property.includes(':caption');
const isAnchor = property.includes(':anchor');
const isContent = property.includes(':content');
const isAnnotated = property.includes(':annotated');
- const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
+ const isInk = () => doc?._layout_isSvg && !docProps?.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 = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -122,7 +126,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
if (highlightIndex) {
return {
- highlightStyle: doc._isGroup ? "dotted": highlightStyle,
+ highlightStyle: doc.isGroup ? "dotted": highlightStyle,
highlightColor,
highlightIndex,
highlightStroke: doc.layout_isSvg,
@@ -132,20 +136,21 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
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.Opacity: return docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton);
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize)));
case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
- case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
+ case StyleProp.TitleHeight:
+ return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
case StyleProp.ShowTitle:
return (
(doc &&
- !props?.LayoutTemplateString &&
+ !docProps?.LayoutTemplateString &&
!doc.presentation_targetDoc &&
- !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
+ !docProps?.LayoutTemplateString?.includes(KeyValueBox.name) &&
props?.layout_showTitle?.() !== '' &&
StrCast(
doc._layout_showTitle,
@@ -165,7 +170,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
if (docColor) return docColor;
const docView = props?.DocumentView?.();
- const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
+ const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView._props, StyleProp.BackgroundColor);
return backColor ? lightOrDark(backColor) : undefined;
case StyleProp.BorderRounding:
return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
@@ -218,7 +223,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
? SettingsManager.userBackgroundColor
: doc.annotationOn
? '#00000010' // faint interior for collections on PDFs, images, etc
- : doc?._isGroup
+ : doc?.isGroup
? undefined
: doc._type_collection === CollectionViewType.Stacking ?
(Colors.DARK_GRAY)
@@ -239,7 +244,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
doc?.layout_boxShadow,
doc?._type_collection === CollectionViewType.Pile
? '4px 4px 10px 2px'
- : lockedPosition() || doc?._isGroup || docProps?.LayoutTemplateString
+ : lockedPosition() || doc?.isGroup || docProps?.LayoutTemplateString
? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
: `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
);
@@ -249,7 +254,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
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?.docViewPath().lastElement()?.rootDoc._freeform_useClusters
+ : props?.docViewPath().lastElement()?.Document._freeform_useClusters
? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: NumCast(doc.group, -1) !== -1
? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -259,17 +264,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
}
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 (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
+ if (StrCast(doc?.pointerEvents) && !docProps?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
+ if (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
if (props?.pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
- if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?._isGroup )? undefined: 'all'
+ if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
+ isGroup )? undefined: 'all'
if (props?.isDocumentActive?.() && !props.treeViewDoc) 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:
const lock = () => {
- if (props?.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform) {
+ if (props?.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
return doc?.pointerEvents !== 'none' ? null : (
<div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
<FontAwesomeIcon icon='lock' size="lg" />
@@ -296,7 +302,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
setSelectedVal={
action((dv) => {
(dv as any).select(false);
- (SettingsManager.propertiesWidth = 250);
+ (SettingsManager.Instance.propertiesWidth = 250);
setTimeout(action(() => {
if (PropertiesView.Instance) {
PropertiesView.Instance.CloseAll();
@@ -314,9 +320,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
color={SettingsManager.userColor}
background={showFilterIcon}
items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
- .filter(dv => StrListCast(dv.rootDoc.childFilters).length || StrListCast(dv.rootDoc.childRangeFilters).length)
+ .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
.map(dv => ({
- text: StrCast(dv.rootDoc.title),
+ text: StrCast(dv?.Document.title),
val: dv as any,
style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
} as IListItemProps)) }
@@ -364,7 +370,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
);
}
/**
- * add lock and hide button decorations for the "Dashboards" flyout TreeView
+ * add hide button decorations for the "Dashboards" flyout TreeView
*/
export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
if (doc && property.split(':')[0] === StyleProp.Decorations) {
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index f81cbdaab..4d0f1bf00 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -1,4 +1,4 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
.templating-menu {
position: absolute;
pointer-events: auto;
@@ -40,7 +40,8 @@
height: 100%;
width: 100%;
- .templateToggle, .chromeToggle {
+ .templateToggle,
+ .chromeToggle {
text-align: left;
color: black;
}
@@ -48,4 +49,4 @@
input {
margin-right: 10px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index a3884c9eb..48653f30a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -13,7 +13,7 @@ import { CollectionTreeView } from './collections/CollectionTreeView';
import { DocumentView } from './nodes/DocumentView';
import { DefaultStyleProvider } from './StyleProvider';
import './TemplateMenu.scss';
-import React = require('react');
+import * as React from 'react';
@observer
class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
@@ -119,7 +119,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- rootSelected={returnFalse}
onCheckedClick={this.scriptField}
onChildClick={this.scriptField}
isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx
index 969605be9..530c693a7 100644
--- a/src/client/views/TouchScrollableMenu.tsx
+++ b/src/client/views/TouchScrollableMenu.tsx
@@ -1,6 +1,6 @@
-import React = require("react");
-import { computed } from "mobx";
-import { observer } from "mobx-react";
+import * as React from 'react';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
export interface TouchScrollableMenuProps {
options: JSX.Element[];
@@ -24,31 +24,35 @@ export interface TouchScrollableMenuItemProps {
@observer
export default class TouchScrollableMenu extends React.Component<TouchScrollableMenuProps> {
-
@computed
- private get possibilities() { return this.props.options; }
+ private get possibilities() {
+ return this.props.options;
+ }
@computed
- private get selectedIndex() { return this.props.selectedIndex; }
+ private get selectedIndex() {
+ return this.props.selectedIndex;
+ }
render() {
return (
- <div className="inkToTextDoc-cont" style={{
- transform: `translate(${this.props.x}px, ${this.props.y}px)`,
- width: 300,
- height: this.possibilities.length * 25
- }}>
+ <div
+ className="inkToTextDoc-cont"
+ style={{
+ transform: `translate(${this.props.x}px, ${this.props.y}px)`,
+ width: 300,
+ height: this.possibilities.length * 25,
+ }}>
<div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-this.selectedIndex * 25}px)` }}>
{this.possibilities}
</div>
- <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}>
- </div>
+ <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}></div>
</div>
);
}
}
-export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps>{
+export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps> {
render() {
return (
<div className="menuItem-cont" onClick={this.props.onClick}>
@@ -56,4 +60,4 @@ export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenu
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index c99281323..db69d6e44 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables';
+@import './global/globalCssVariables.module.scss';
// this file is for overriding all the css from installed node modules
// goldenlayout stuff
diff --git a/src/client/views/animationtimeline/Region.scss b/src/client/views/animationtimeline/Region.scss
index f7476ab55..b390ae34e 100644
--- a/src/client/views/animationtimeline/Region.scss
+++ b/src/client/views/animationtimeline/Region.scss
@@ -1,4 +1,4 @@
-@import './../global/globalCssVariables.scss';
+@import './../global/globalCssVariables.module.scss';
$timelineColor: #9acedf;
$timelineDark: #77a1aa;
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 53c5c4718..15cbbc16f 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -6,10 +6,11 @@ import { List } from '../../../fields/List';
import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema';
import { Cast, NumCast } from '../../../fields/Types';
import { Transform } from '../../util/Transform';
-import '../global/globalCssVariables.scss';
+import '../global/globalCssVariables.module.scss';
import './Region.scss';
import './Timeline.scss';
import { TimelineMenu } from './TimelineMenu';
+import { ObservableReactComponent } from '../ObservableReactComponent';
/**
* Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
@@ -31,11 +32,11 @@ export namespace RegionHelpers {
let rightMost: RegionData | undefined = undefined;
regions.forEach(region => {
const neighbor = RegionData(region);
- if (currentRegion.position! > neighbor.position) {
+ if (NumCast(currentRegion.position) > neighbor.position) {
if (!leftMost || neighbor.position > leftMost.position) {
leftMost = neighbor;
}
- } else if (currentRegion.position! < neighbor.position) {
+ } else if (NumCast(currentRegion.position) < neighbor.position) {
if (!rightMost || neighbor.position < rightMost.position) {
rightMost = neighbor;
}
@@ -156,44 +157,45 @@ interface IProps {
* @author Andrew Kim
*/
@observer
-export class Region extends React.Component<IProps> {
+export class Region extends ObservableReactComponent<IProps> {
@observable private _bar = React.createRef<HTMLDivElement>();
@observable private _mouseToggled = false;
@observable private _doubleClickEnabled = false;
@computed private get regiondata() {
- return RegionData(this.props.RegionData);
+ return RegionData(this._props.RegionData);
}
@computed private get regions() {
- return DocListCast(this.props.animatedDoc.regions);
+ return DocListCast(this._props.animatedDoc.regions);
}
@computed private get keyframes() {
return DocListCast(this.regiondata.keyframes);
}
@computed private get pixelPosition() {
- return RegionHelpers.convertPixelTime(this.regiondata.position, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+ return RegionHelpers.convertPixelTime(this.regiondata.position, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
}
@computed private get pixelDuration() {
- return RegionHelpers.convertPixelTime(this.regiondata.duration, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+ return RegionHelpers.convertPixelTime(this.regiondata.duration, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
}
@computed private get pixelFadeIn() {
- return RegionHelpers.convertPixelTime(this.regiondata.fadeIn, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+ return RegionHelpers.convertPixelTime(this.regiondata.fadeIn, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
}
@computed private get pixelFadeOut() {
- return RegionHelpers.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+ return RegionHelpers.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
}
constructor(props: any) {
super(props);
+ makeObservable(this);
}
componentDidMount() {
setTimeout(() => {
//giving it a temporary 1sec delay...
if (!this.regiondata.keyframes) this.regiondata.keyframes = new List<Doc>();
- const start = this.props.makeKeyData(this.regiondata, this.regiondata.position, RegionHelpers.KeyframeType.end);
- const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, RegionHelpers.KeyframeType.fade);
- const fadeOut = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, RegionHelpers.KeyframeType.fade);
- const finish = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration, RegionHelpers.KeyframeType.end);
+ const start = this._props.makeKeyData(this.regiondata, this.regiondata.position, RegionHelpers.KeyframeType.end);
+ const fadeIn = this._props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, RegionHelpers.KeyframeType.fade);
+ const fadeOut = this._props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, RegionHelpers.KeyframeType.fade);
+ const finish = this._props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration, RegionHelpers.KeyframeType.end);
fadeIn.opacity = 1;
fadeOut.opacity = 1;
start.opacity = 0.1;
@@ -212,7 +214,7 @@ export class Region extends React.Component<IProps> {
this._doubleClickEnabled = false;
} else {
setTimeout(() => {
- if (!this._mouseToggled && this._doubleClickEnabled) this.props.changeCurrentBarX(this.pixelPosition + (clientX - this._bar.current!.getBoundingClientRect().left) * this.props.transform.Scale);
+ if (!this._mouseToggled && this._doubleClickEnabled) this._props.changeCurrentBarX(this.pixelPosition + (clientX - this._bar.current!.getBoundingClientRect().left) * this._props.transform.Scale);
this._mouseToggled = false;
this._doubleClickEnabled = false;
}, 200);
@@ -234,7 +236,7 @@ export class Region extends React.Component<IProps> {
const left = RegionHelpers.findAdjacentRegion(RegionHelpers.Direction.left, this.regiondata, this.regions)!;
const right = RegionHelpers.findAdjacentRegion(RegionHelpers.Direction.right, this.regiondata, this.regions)!;
const prevX = this.regiondata.position;
- const futureX = this.regiondata.position + RegionHelpers.convertPixelTime(e.movementX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
+ const futureX = this.regiondata.position + RegionHelpers.convertPixelTime(e.movementX, 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement);
if (futureX <= 0) {
this.regiondata.position = 0;
} else if (left && left.position + left.duration >= futureX) {
@@ -273,7 +275,7 @@ export class Region extends React.Component<IProps> {
e.preventDefault();
e.stopPropagation();
const bar = this._bar.current!;
- const offset = RegionHelpers.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
+ const offset = RegionHelpers.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this._props.transform.Scale), 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement);
const leftRegion = RegionHelpers.findAdjacentRegion(RegionHelpers.Direction.left, this.regiondata, this.regions);
if (leftRegion && this.regiondata.position + offset <= leftRegion.position + leftRegion.duration) {
this.regiondata.position = leftRegion.position + leftRegion.duration;
@@ -297,7 +299,7 @@ export class Region extends React.Component<IProps> {
e.preventDefault();
e.stopPropagation();
const bar = this._bar.current!;
- const offset = RegionHelpers.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
+ const offset = RegionHelpers.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this._props.transform.Scale), 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement);
const rightRegion = RegionHelpers.findAdjacentRegion(RegionHelpers.Direction.right, this.regiondata, this.regions);
const fadeOutKeyframeTime = NumCast(this.keyframes[this.keyframes.length - 3].time);
if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= fadeOutKeyframeTime) {
@@ -316,13 +318,13 @@ export class Region extends React.Component<IProps> {
createKeyframe = (clientX: number) => {
this._mouseToggled = true;
const bar = this._bar.current!;
- const offset = RegionHelpers.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
+ const offset = RegionHelpers.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this._props.transform.Scale), 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement);
if (offset > this.regiondata.fadeIn && offset < this.regiondata.duration - this.regiondata.fadeOut) {
//make sure keyframe is not created inbetween fades and ends
const position = this.regiondata.position;
- this.props.makeKeyData(this.regiondata, Math.round(position + offset), RegionHelpers.KeyframeType.default);
+ this._props.makeKeyData(this.regiondata, Math.round(position + offset), RegionHelpers.KeyframeType.default);
this.regiondata.hasData = true;
- this.props.changeCurrentBarX(RegionHelpers.convertPixelTime(Math.round(position + offset), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied
+ this._props.changeCurrentBarX(RegionHelpers.convertPixelTime(Math.round(position + offset), 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied
}
};
@@ -330,7 +332,7 @@ export class Region extends React.Component<IProps> {
moveKeyframe = (e: React.MouseEvent, kf: Doc) => {
e.preventDefault();
e.stopPropagation();
- this.props.changeCurrentBarX(RegionHelpers.convertPixelTime(NumCast(kf.time!), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement));
+ this._props.changeCurrentBarX(RegionHelpers.convertPixelTime(NumCast(kf.time!), 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement));
};
/**
@@ -373,7 +375,7 @@ export class Region extends React.Component<IProps> {
*/
@action
makeRegionMenu = (kf: Doc, e: MouseEvent) => {
- TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.animatedDoc.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)),
+ TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this._props.animatedDoc.regions, listSpec(Doc))?.splice(this.regions.indexOf(this._props.RegionData), 1)),
TimelineMenu.Instance.addItem('input', `fadeIn: ${this.regiondata.fadeIn}ms`, val => {
runInAction(() => {
let cannotMove: boolean = false;
@@ -459,7 +461,7 @@ export class Region extends React.Component<IProps> {
e.stopPropagation();
const div = ref.current!;
div.style.opacity = '1';
- Doc.BrushDoc(this.props.animatedDoc);
+ Doc.BrushDoc(this._props.animatedDoc);
};
/**
@@ -471,7 +473,7 @@ export class Region extends React.Component<IProps> {
e.stopPropagation();
const div = ref.current!;
div.style.opacity = '0';
- Doc.UnBrushDoc(this.props.animatedDoc);
+ Doc.UnBrushDoc(this._props.animatedDoc);
};
///////////////////////UI STUFF /////////////////////////
@@ -485,12 +487,12 @@ export class Region extends React.Component<IProps> {
return DocListCast(this.regiondata.keyframes).map(kf => {
return (
<>
- <div className="keyframe" style={{ left: `${RegionHelpers.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}>
+ <div className="keyframe" style={{ left: `${RegionHelpers.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement) - this.pixelPosition}px` }}>
<div className="divider"></div>
<div
className="keyframeCircle keyframe-indicator"
style={{
- borderColor: this.props.saveStateKf === kf ? 'red' : undefined,
+ borderColor: this._props.saveStateKf === kf ? 'red' : undefined,
}}
onPointerDown={e => {
e.preventDefault();
@@ -525,8 +527,8 @@ export class Region extends React.Component<IProps> {
if (index !== this.keyframes.length - 1) {
const right = this.keyframes[index + 1];
const bodyRef = React.createRef<HTMLDivElement>();
- const kfPos = RegionHelpers.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
- const rightPos = RegionHelpers.convertPixelTime(NumCast(right.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+ const kfPos = RegionHelpers.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
+ const rightPos = RegionHelpers.convertPixelTime(NumCast(right.time), 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
keyframeDividers.push(
<div
ref={bodyRef}
diff --git a/src/client/views/animationtimeline/Timeline.scss b/src/client/views/animationtimeline/Timeline.scss
index 48422b789..35ba0fa7f 100644
--- a/src/client/views/animationtimeline/Timeline.scss
+++ b/src/client/views/animationtimeline/Timeline.scss
@@ -1,4 +1,4 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
$timelineColor: #9acedf;
$timelineDark: #77a1aa;
@@ -30,7 +30,6 @@ $timelineDark: #77a1aa;
color: $timelineColor;
margin-left: 3px;
}
-
}
.grid-box {
@@ -61,7 +60,7 @@ $timelineDark: #77a1aa;
-webkit-transform: scale(1.1);
-ms-transform: scale(1.1);
transform: scale(1.1);
- transition: .2s ease;
+ transition: 0.2s ease;
}
}
@@ -128,7 +127,6 @@ $timelineDark: #77a1aa;
// margin-top: 0.5px;
}
}
-
}
.time-input {
@@ -154,7 +152,7 @@ $timelineDark: #77a1aa;
.number-label {
color: black;
transform: rotate(-90deg) translate(-15px, 8px);
- font-size: .85em;
+ font-size: 0.85em;
}
.timeline-container {
@@ -178,7 +176,6 @@ $timelineDark: #77a1aa;
background-color: transparent;
height: 30px;
width: 100%;
-
}
.scrubber {
@@ -217,7 +214,6 @@ $timelineDark: #77a1aa;
position: absolute;
// box-shadow: -10px 0px 10px 10px red;
}
-
}
.currentTime {
@@ -266,7 +262,6 @@ $timelineDark: #77a1aa;
p {
hyphens: auto;
}
-
}
}
@@ -280,8 +275,6 @@ $timelineDark: #77a1aa;
}
}
-
-
.overview {
position: absolute;
height: 50px;
@@ -298,7 +291,6 @@ $timelineDark: #77a1aa;
}
}
-
.timeline-checker {
height: auto;
width: auto;
@@ -312,11 +304,11 @@ $timelineDark: #77a1aa;
width: auto;
overflow: hidden;
margin: 0px 10px;
- cursor: pointer
+ cursor: pointer;
}
.check {
width: 50px;
height: 50px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 4be3b05ab..f27a2d2fd 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -1,7 +1,7 @@
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -14,6 +14,7 @@ import { RegionHelpers } from './Region';
import './Timeline.scss';
import { TimelineOverview } from './TimelineOverview';
import { Track } from './Track';
+import { ObservableReactComponent } from '../ObservableReactComponent';
/**
* Timeline class controls most of timeline functions besides individual region and track mechanism. Main functions are
@@ -43,7 +44,7 @@ import { Track } from './Track';
*/
@observer
-export class Timeline extends React.Component<FieldViewProps> {
+export class Timeline extends ObservableReactComponent<FieldViewProps> {
//readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
@@ -54,6 +55,11 @@ export class Timeline extends React.Component<FieldViewProps> {
private DEFAULT_CONTAINER_HEIGHT: number = 330;
private MIN_CONTAINER_HEIGHT: number = 205;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
//react refs
@observable private _trackbox = React.createRef<HTMLDivElement>();
@observable private _titleContainer = React.createRef<HTMLDivElement>();
@@ -82,11 +88,11 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@computed
private get children(): Doc[] {
- const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this.props.Document.type) as any);
+ const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as any);
if (annotatedDoc) {
- return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '_annotations']);
+ return DocListCast(this._props.Document[Doc.LayoutFieldKey(this._props.Document) + '_annotations']);
}
- return DocListCast(this.props.Document[this.props.fieldKey]);
+ return DocListCast(this._props.Document[this._props.fieldKey]);
}
/////////lifecycle functions////////////
@@ -96,21 +102,21 @@ export class Timeline extends React.Component<FieldViewProps> {
this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
- if (!this.props.Document.AnimationLength) {
+ if (!this._props.Document.AnimationLength) {
//if animation length did not exist
- this.props.Document.AnimationLength = this._time; //set it to default time
+ 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._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
}
/////////////////////////////////////////////////
@@ -206,7 +212,7 @@ export class Timeline extends React.Component<FieldViewProps> {
onScrubberMove = (e: PointerEvent) => {
const scrubberbox = this._infoContainer.current!;
const left = scrubberbox.getBoundingClientRect().left;
- const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale;
+ const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale;
this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
return false;
};
@@ -233,7 +239,7 @@ export class Timeline extends React.Component<FieldViewProps> {
this._visibleStart -= e.movementX;
this._totalLength -= e.movementX;
this._time -= RegionHelpers.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
- this.props.Document.AnimationLength = this._time;
+ this._props.Document.AnimationLength = this._time;
}
return false;
};
@@ -349,8 +355,8 @@ export class Timeline extends React.Component<FieldViewProps> {
private timelineToolBox = (scale: number, totalTime: number) => {
const size = 40 * scale; //50 is default
const iconSize = 25;
- const width: number = this.props.PanelWidth();
- const modeType = this.props.Document.isATOn ? 'Author' : 'Play';
+ const 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
// if its less than 950 pixels then it's going to be overlapping
@@ -389,7 +395,7 @@ export class Timeline extends React.Component<FieldViewProps> {
tickIncrement={this._tickIncrement}
time={this._time}
parent={this}
- isAuthoring={BoolCast(this.props.Document.isATOn)}
+ isAuthoring={BoolCast(this._props.Document.isATOn)}
currentBarX={this._currentBarX}
totalLength={this._totalLength}
visibleLength={this._visibleLength}
@@ -410,10 +416,10 @@ export class Timeline extends React.Component<FieldViewProps> {
</div>
<div className="time-box overview-tool" style={{ display: 'flex' }}>
{this.timeIndicator(lengthString, totalTime)}
- <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}>
+ <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this._props.Document)}>
<FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
</div>
- <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this.props.Document)}>
+ <div className="resetView-tool" style={{ display: this._props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this._props.Document)}>
<FontAwesomeIcon icon="expand-arrows-alt" size="lg" />
</div>
</div>
@@ -423,17 +429,17 @@ export class Timeline extends React.Component<FieldViewProps> {
};
timeIndicator(lengthString: string, totalTime: number) {
- if (this.props.Document.isATOn) {
- return <div key="time-text" className="animation-text" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', display: this.props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
+ if (this._props.Document.isATOn) {
+ return <div key="time-text" className="animation-text" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', display: this._props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
} else {
const ctime = `Current: ${this.getCurrentTime()}`;
const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
<div style={{ flexDirection: 'column' }}>
- <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
{ctime}
</div>
- <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
{ttime}
</div>
</div>
@@ -459,8 +465,8 @@ export class Timeline extends React.Component<FieldViewProps> {
const roundToggleContainer = this._roundToggleContainerRef.current!;
const timelineContainer = this._timelineContainer.current!;
- this.props.Document.isATOn = !this.props.Document.isATOn;
- if (!BoolCast(this.props.Document.isATOn)) {
+ this._props.Document.isATOn = !this._props.Document.isATOn;
+ if (!BoolCast(this._props.Document.isATOn)) {
//turning on playmode...
roundToggle.style.transform = 'translate(0px, 0px)';
roundToggle.style.animationName = 'turnoff';
@@ -535,7 +541,7 @@ export class Timeline extends React.Component<FieldViewProps> {
// change visible and total width
return (
<div style={{ visibility: 'visible' }}>
- <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
+ <div key="timeline_wrapper" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
<div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
<div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
{this.drawTicks()}
@@ -543,18 +549,18 @@ export class Timeline extends React.Component<FieldViewProps> {
<div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown}></div>
</div>
<div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
- {[...this.children, this.props.Document].map(doc => (
+ {[...this.children, this._props.Document].map(doc => (
<Track
ref={ref => this.mapOfTracks.push(ref)}
timeline={this}
animatedDoc={doc}
currentBarX={this._currentBarX}
changeCurrentBarX={this.changeCurrentBarX}
- transform={this.props.ScreenToLocalTransform()}
+ transform={this._props.ScreenToLocalTransform()}
time={this._time}
tickSpacing={this._tickSpacing}
tickIncrement={this._tickIncrement}
- collection={this.props.Document}
+ collection={this._props.Document}
timelineVisible={true}
/>
))}
@@ -562,7 +568,7 @@ export class Timeline extends React.Component<FieldViewProps> {
</div>
<div className="currentTime">Current: {this.getCurrentTime()}</div>
<div key="timeline_title" className="title-container" ref={this._titleContainer}>
- {[...this.children, this.props.Document].map(doc => (
+ {[...this.children, this._props.Document].map(doc => (
<div style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
<p>{StrCast(doc.title)}</p>
</div>
diff --git a/src/client/views/animationtimeline/TimelineMenu.scss b/src/client/views/animationtimeline/TimelineMenu.scss
index 43a89419e..de2042f17 100644
--- a/src/client/views/animationtimeline/TimelineMenu.scss
+++ b/src/client/views/animationtimeline/TimelineMenu.scss
@@ -1,56 +1,49 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
-
-.timeline-menu-container{
+.timeline-menu-container {
position: absolute;
display: flex;
box-shadow: $medium-gray 0.2vw 0.2vw 0.4vw;
flex-direction: column;
background: whitesmoke;
z-index: 10000;
- width: 200px;
+ width: 200px;
padding-bottom: 10px;
border-radius: 15px;
- border: solid #BBBBBBBB 1px;
-
-
+ border: solid #bbbbbbbb 1px;
- .timeline-menu-input{
- font: $sans-serif;
- font-size: 13px;
- width:100%;
- text-transform: uppercase;
- letter-spacing: 2px;
- margin-left: 10px;
- background-color: transparent;
- border-width: 0px;
- transition: border-width 500ms;
+ .timeline-menu-input {
+ font: $sans-serif;
+ font-size: 13px;
+ width: 100%;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ margin-left: 10px;
+ background-color: transparent;
+ border-width: 0px;
+ transition: border-width 500ms;
}
- .timeline-menu-input:hover{
- border-width: 2px;
+ .timeline-menu-input:hover {
+ border-width: 2px;
}
-
-
-
- .timeline-menu-header{
- border-top-left-radius: 15px;
- border-top-right-radius: 15px;
- text-transform: uppercase;
- background: $dark-gray;
- letter-spacing: 2px;
+ .timeline-menu-header {
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
+ text-transform: uppercase;
+ background: $dark-gray;
+ letter-spacing: 2px;
- .timeline-menu-header-desc{
- font:$sans-serif;
- font-size: 13px;
- text-align: center;
- color: whitesmoke;
+ .timeline-menu-header-desc {
+ font: $sans-serif;
+ font-size: 13px;
+ text-align: center;
+ color: whitesmoke;
}
}
-
.timeline-menu-item {
// width: 11vw; //10vw
height: 30px; //2vh
@@ -64,7 +57,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
@@ -73,22 +66,21 @@
letter-spacing: 2px;
text-transform: uppercase;
padding-right: 20px;
- padding-left: 10px;
+ padding-left: 10px;
}
.timeline-menu-item:hover {
- border-width: .11px;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray;
border-bottom-style: solid;
border-top-style: solid;
- background: $medium-blue;
+ background: $medium-blue;
}
.timeline-menu-desc {
- padding-left: 10px;
- font:$sans-serif;
- font-size: 13px;
+ padding-left: 10px;
+ font: $sans-serif;
+ font-size: 13px;
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 1769c41bd..97a571dc4 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,7 +1,7 @@
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Utils } from '../../../Utils';
@@ -16,8 +16,9 @@ export class TimelineMenu extends React.Component {
@observable private _y = 0;
@observable private _currentMenu: JSX.Element[] = [];
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
+ makeObservable(this);
TimelineMenu.Instance = this;
}
diff --git a/src/client/views/animationtimeline/TimelineOverview.scss b/src/client/views/animationtimeline/TimelineOverview.scss
index c8d96c399..2878232e6 100644
--- a/src/client/views/animationtimeline/TimelineOverview.scss
+++ b/src/client/views/animationtimeline/TimelineOverview.scss
@@ -1,4 +1,4 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
$timelineColor: #9acedf;
$timelineDark: #77a1aa;
@@ -66,8 +66,6 @@ $timelineDark: #77a1aa;
}
}
-
-
.timeline-play-bar {
position: relative;
padding: 0px;
@@ -104,4 +102,4 @@ $timelineDark: #77a1aa;
border-radius: 20px;
margin-top: -4px;
cursor: pointer;
-} \ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/Track.scss b/src/client/views/animationtimeline/Track.scss
index f45e0556d..f56b2fe5f 100644
--- a/src/client/views/animationtimeline/Track.scss
+++ b/src/client/views/animationtimeline/Track.scss
@@ -1,7 +1,6 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
.track-container {
-
.track {
.inner {
top: 0px;
@@ -12,4 +11,4 @@
z-index: 100;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index d959241d0..00aa51cac 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,4 +1,4 @@
-import { action, computed, intercept, observable, reaction, runInAction } from 'mobx';
+import { action, computed, intercept, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
@@ -11,6 +11,7 @@ import { Transform } from '../../util/Transform';
import { Region, RegionData, RegionHelpers } from './Region';
import { Timeline } from './Timeline';
import './Track.scss';
+import { ObservableReactComponent } from '../ObservableReactComponent';
interface IProps {
timeline: Timeline;
@@ -26,7 +27,7 @@ interface IProps {
}
@observer
-export class Track extends React.Component<IProps> {
+export class Track extends ObservableReactComponent<IProps> {
@observable private _inner = React.createRef<HTMLDivElement>();
@observable private _currentBarXReaction: any;
@observable private _timelineVisibleReaction: any;
@@ -37,24 +38,29 @@ export class Track extends React.Component<IProps> {
private primitiveWhitelist = ['x', 'y', '_freeform_panX', '_freeform_panY', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
private objectWhitelist = ['data'];
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed private get regions() {
- return DocListCast(this.props.animatedDoc.regions);
+ return DocListCast(this._props.animatedDoc.regions);
}
@computed private get time() {
- return NumCast(RegionHelpers.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+ return NumCast(RegionHelpers.convertPixelTime(this._props.currentBarX, 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement));
}
componentDidMount() {
- DocListCastAsync(this.props.animatedDoc.regions).then(regions => {
- if (!regions) this.props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
+ DocListCastAsync(this._props.animatedDoc.regions).then(regions => {
+ if (!regions) this._props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
//these two lines are exactly same from timeline.tsx
const relativeHeight = window.innerHeight / 20;
runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness
this._timelineVisibleReaction = this.timelineVisibleReaction();
this._currentBarXReaction = this.currentBarXReaction();
- if (DocListCast(this.props.animatedDoc.regions).length === 0) this.createRegion(this.time);
- this.props.animatedDoc.hidden = false;
- this.props.animatedDoc.opacity = 1;
+ if (DocListCast(this._props.animatedDoc.regions).length === 0) this.createRegion(this.time);
+ this._props.animatedDoc.hidden = false;
+ this._props.animatedDoc.opacity = 1;
// this.autoCreateKeyframe();
});
}
@@ -88,7 +94,7 @@ export class Track extends React.Component<IProps> {
*/
@action
saveKeyframe = () => {
- if (this.props.timeline.IsPlaying || !this.saveStateRegion || !this.saveStateKf) {
+ if (this._props.timeline.IsPlaying || !this.saveStateRegion || !this.saveStateKf) {
this.saveStateKf = undefined;
this.saveStateRegion = undefined;
return;
@@ -130,13 +136,13 @@ export class Track extends React.Component<IProps> {
*/
@action
autoCreateKeyframe = () => {
- const objects = this.objectWhitelist.map(key => this.props.animatedDoc[key]);
- intercept(this.props.animatedDoc, change => {
+ const objects = this.objectWhitelist.map(key => this._props.animatedDoc[key]);
+ intercept(this._props.animatedDoc, change => {
return change;
});
return reaction(
() => {
- return [...this.primitiveWhitelist.map(key => this.props.animatedDoc[key]), ...objects];
+ return [...this.primitiveWhitelist.map(key => this._props.animatedDoc[key]), ...objects];
},
(changed, reaction) => {
//check for region
@@ -170,18 +176,18 @@ export class Track extends React.Component<IProps> {
@action
currentBarXReaction = () => {
return reaction(
- () => this.props.currentBarX,
+ () => this._props.currentBarX,
() => {
const regiondata = this.findRegion(this.time);
if (regiondata) {
- this.props.animatedDoc.hidden = false;
+ this._props.animatedDoc.hidden = false;
// if (!this._autoKfReaction) {
// // this._autoKfReaction = this.autoCreateKeyframe();
// }
this.timeChange();
} else {
- this.props.animatedDoc.hidden = true;
- this.props.animatedDoc !== this.props.collection && (this.props.animatedDoc.opacity = 0);
+ this._props.animatedDoc.hidden = true;
+ this._props.animatedDoc !== this._props.collection && (this._props.animatedDoc.opacity = 0);
//if (this._autoKfReaction) this._autoKfReaction();
}
}
@@ -195,7 +201,7 @@ export class Track extends React.Component<IProps> {
timelineVisibleReaction = () => {
return reaction(
() => {
- return this.props.timelineVisible;
+ return this._props.timelineVisible;
},
isVisible => {
if (isVisible) {
@@ -252,14 +258,14 @@ export class Track extends React.Component<IProps> {
@action
private applyKeys = (kf: Doc) => {
this.primitiveWhitelist.forEach(key => {
- if (key === 'opacity' && this.props.animatedDoc === this.props.collection) {
+ if (key === 'opacity' && this._props.animatedDoc === this._props.collection) {
return;
}
if (!kf[key]) {
- this.props.animatedDoc[key] = undefined;
+ this._props.animatedDoc[key] = undefined;
} else {
const stored = kf[key];
- this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
+ this._props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
};
@@ -283,7 +289,7 @@ export class Track extends React.Component<IProps> {
@action
interpolate = (left: Doc, right: Doc) => {
this.primitiveWhitelist.forEach(key => {
- if (key === 'opacity' && this.props.animatedDoc === this.props.collection) {
+ if (key === 'opacity' && this._props.animatedDoc === this._props.collection) {
return;
}
if (typeof left[key] === 'number' && typeof right[key] === 'number') {
@@ -291,11 +297,11 @@ export class Track extends React.Component<IProps> {
const dif = NumCast(right[key]) - NumCast(left[key]);
const deltaLeft = this.time - NumCast(left.time);
const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
- this.props.animatedDoc[key] = NumCast(left[key]) + dif * ratio;
+ this._props.animatedDoc[key] = NumCast(left[key]) + dif * ratio;
} else {
// case data
const stored = left[key];
- this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
+ this._props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
};
@@ -314,8 +320,8 @@ export class Track extends React.Component<IProps> {
@action
onInnerDoubleClick = (e: React.MouseEvent) => {
const inner = this._inner.current!;
- const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
- this.createRegion(RegionHelpers.convertPixelTime(offsetX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+ const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this._props.transform.Scale);
+ this.createRegion(RegionHelpers.convertPixelTime(offsetX, 'mili', 'time', this._props.tickSpacing, this._props.tickIncrement));
};
/**
@@ -335,7 +341,7 @@ export class Track extends React.Component<IProps> {
regiondata.duration = rightRegion.position - regiondata.position;
}
if (this.regions.length === 0 || !rightRegion || (rightRegion && rightRegion.position - regiondata.position >= NumCast(regiondata.fadeIn) + NumCast(regiondata.fadeOut))) {
- Cast(this.props.animatedDoc.regions, listSpec(Doc))?.push(regiondata);
+ Cast(this._props.animatedDoc.regions, listSpec(Doc))?.push(regiondata);
this._newKeyframe = true;
this.saveStateRegion = regiondata;
return regiondata;
@@ -370,7 +376,7 @@ export class Track extends React.Component<IProps> {
copyDocDataToKeyFrame = (doc: Doc) => {
var somethingChanged = false;
this.primitiveWhitelist.map(key => {
- const originalVal = this.props.animatedDoc[key];
+ const originalVal = this._props.animatedDoc[key];
somethingChanged = somethingChanged || originalVal !== doc[key];
if (doc.type === RegionHelpers.KeyframeType.end && key === 'opacity') doc.opacity = 0;
else doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal;
@@ -391,10 +397,10 @@ export class Track extends React.Component<IProps> {
ref={this._inner}
style={{ height: `${this._trackHeight}px` }}
onDoubleClick={this.onInnerDoubleClick}
- onPointerOver={() => Doc.BrushDoc(this.props.animatedDoc)}
- onPointerOut={() => Doc.UnBrushDoc(this.props.animatedDoc)}>
+ onPointerOver={() => Doc.BrushDoc(this._props.animatedDoc)}
+ onPointerOut={() => Doc.UnBrushDoc(this._props.animatedDoc)}>
{this.regions?.map((region, i) => {
- return <Region key={`${i}`} {...this.props} saveStateKf={saveStateKf} RegionData={region} makeKeyData={this.makeKeyData} />;
+ return <Region key={`${i}`} {...this._props} saveStateKf={saveStateKf} RegionData={region} makeKeyData={this.makeKeyData} />;
})}
</div>
</div>
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
new file mode 100644
index 000000000..2cec29669
--- /dev/null
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -0,0 +1,107 @@
+import * as React from 'react';
+import { CollectionSubView } from './CollectionSubView';
+import { observer } from 'mobx-react';
+import { computed, makeObservable, observable } from 'mobx';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils';
+import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { TbRuler } from 'react-icons/tb';
+import { Transform } from '../../util/Transform';
+import { DocData } from '../../../fields/DocSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+
+@observer
+export class CollectionCalendarView extends CollectionSubView() {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ componentDidMount(): void {}
+
+ componentWillUnmount(): void {}
+
+ @computed get allCalendars() {
+ return this.childDocs; // returns a list of docs (i.e. calendars)
+ }
+
+ removeCalendar = () => {};
+
+ addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+ // bring up calendar modal with option to create a calendar
+ return 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
+ };
+
+ // most recent calendar should come first
+ sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
+ const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range);
+ const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range);
+
+ const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr);
+ const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr);
+
+ if (aFromDate > bFromDate) {
+ return -1; // a comes first
+ } else if (aFromDate < bFromDate) {
+ 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
+ }
+ }
+ };
+
+ screenToLocalTransform = () =>
+ this._props
+ .ScreenToLocalTransform()
+ .translate(Doc.NativeWidth(this._props.Document), 0)
+ .scale(this._props.NativeDimScaling?.() || 1);
+
+ get calendarsKey() {
+ return this._props.fieldKey;
+ }
+
+ render() {
+ return (
+ <div className="collectionCalendarView">
+ <CollectionStackingView
+ {...this._props}
+ setContentView={emptyFunction}
+ ref={this._stackRef}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this._props.PanelWidth}
+ // childFilters={this.childFilters} DO I NEED THIS?
+ sortFunc={this.sortByMostRecentDate}
+ setHeight={undefined}
+ isAnnotationOverlay={false}
+ // select={emptyFunction} What does this mean?
+ isAnyChildContentActive={returnTrue} // ??
+ // childDocumentsActive={}
+ // whenChildContentsActiveChanged={}
+ childHideDecorationTitle={false}
+ removeDocument={this.removeDocument} // will calendar automatically be removed from myCalendars
+ moveDocument={this.moveDocument}
+ addDocument={this.addCalendar}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ renderDepth={this._props.renderDepth + 1}
+ fieldKey={this.calendarsKey}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 8319f19ca..a556d0fa7 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -1,8 +1,9 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.collectionCarousel3DView-outer {
height: 100%;
position: relative;
background-color: white;
+ overflow: hidden;
}
.carousel-wrapper {
@@ -16,7 +17,9 @@
.collectionCarousel3DView-item,
.collectionCarousel3DView-item-active {
flex: 1;
- transition: opacity 0.3s linear, transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+ transition:
+ opacity 0.3s linear,
+ transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
pointer-events: none;
opacity: 0.5;
z-index: 1;
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index a8d080953..a11c53d4d 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,25 +1,28 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { Utils, returnFalse, returnZero } from '../../../Utils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { returnFalse, returnZero, Utils } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
-import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import { StyleProp } from '../StyleProvider';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
-
+const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
@computed get scrollSpeed() {
return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
}
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -61,15 +64,16 @@ export class CollectionCarousel3DView extends CollectionSubView() {
return (
<DocumentView
{...this.props}
+ Document={childPair.layout}
+ TemplateDataDocument={childPair.data}
+ //suppressSetHeight={true}
NativeWidth={returnZero}
NativeHeight={returnZero}
- //suppressSetHeight={true}
+ layout_fitWidth={undefined}
onDoubleClick={this.onChildDoubleClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
- Document={childPair.layout}
- DataDoc={childPair.data}
focus={this.focus}
ScreenToLocalTransform={this.childScreenToLocal}
isContentActive={this.isChildContentActive}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 33a92d406..5d09f14ef 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,14 +1,14 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, returnOne, returnZero, StopEvent } from '../../../Utils';
+import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
+import { StyleProp } from '../StyleProvider';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
import './CollectionCarouselView.scss';
import { CollectionSubView } from './CollectionSubView';
@@ -16,6 +16,11 @@ import { CollectionSubView } from './CollectionSubView';
export class CollectionCarouselView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentWillUnmount() {
this._dropDisposer?.();
}
@@ -51,7 +56,7 @@ export class CollectionCarouselView extends CollectionSubView() {
const index = NumCast(this.layoutDoc._carousel_index);
const curDoc = this.childLayoutPairs?.[index];
const captionProps = { ...this.props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
- const show_captions = StrCast(this.layoutDoc._layout_showCaption);
+ const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
return !(curDoc?.layout instanceof Doc) ? null : (
<>
<div className="collectionCarouselView-image" key="image">
@@ -59,34 +64,36 @@ export class CollectionCarouselView extends CollectionSubView() {
{...this.props}
NativeWidth={returnZero}
NativeHeight={returnZero}
+ layout_fitWidth={undefined}
setContentView={undefined}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive}
isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
- hideCaptions={show_captions ? true : false}
+ hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
Document={curDoc.layout}
- DataDoc={curDoc.layout.resolvedDataDoc as Doc}
+ TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)}
PanelHeight={this.panelHeight}
bringToFront={returnFalse}
/>
</div>
- <div
- className="collectionCarouselView-caption"
- key="caption"
- onWheel={StopEvent}
- style={{
- display: show_captions ? undefined : 'none',
- borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
- marginRight: this.marginX,
- marginLeft: this.marginX,
- width: `calc(100% - ${this.marginX * 2}px)`,
- }}>
- <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
- </div>
+ {!carouselShowsCaptions ? null : (
+ <div
+ className="collectionCarouselView-caption"
+ key="caption"
+ onWheel={StopEvent}
+ style={{
+ borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
+ marginRight: this.marginX,
+ marginLeft: this.marginX,
+ width: `calc(100% - ${this.marginX * 2}px)`,
+ }}>
+ <FormattedTextBox key={index} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
+ </div>
+ )}
</>
);
}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 333ba9f32..a747ef45f 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.lm_root {
position: relative;
@@ -28,10 +28,6 @@
position: relative;
z-index: 20;
}
-.lm_splitter:hover,
-.lm_splitter.lm_dragging {
- background: orange;
-}
.lm_splitter.lm_vertical .lm_drag_handle {
width: 100%;
position: absolute;
@@ -276,7 +272,7 @@
z-index: 20;
} /*# sourceMappingURL=goldenlayout-base.css.map */
-@import '../../../../node_modules/golden-layout/src/css/goldenlayout-dark-theme.css';
+@import './goldenLayoutTheme.css';
.lm_title {
-webkit-appearance: none;
@@ -681,11 +677,6 @@ ul.lm_tabs::before {
height: 8px;
}
- .flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
- .flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
- background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center;
- }
-
.flexlayout__tab_button_overflow {
float: left;
width: 20px;
@@ -696,7 +687,6 @@ ul.lm_tabs::before {
font-size: 10px;
color: lightgray;
font-family: Arial, sans-serif;
- background: transparent url('../../../../node_modules/flexlayout-react/images/more.png') no-repeat left;
}
.flexlayout__tabset_header {
@@ -751,7 +741,6 @@ ul.lm_tabs::before {
height: 20px;
border: none;
outline-width: 0;
- background: transparent url('../../../../node_modules/flexlayout-react/images/maximize.png') no-repeat center;
}
.flexlayout__tab_toolbar_button-max {
@@ -759,7 +748,6 @@ ul.lm_tabs::before {
height: 20px;
border: none;
outline-width: 0;
- background: transparent url('../../../../node_modules/flexlayout-react/images/restore.png') no-repeat center;
}
.flexlayout__popup_menu_item {
@@ -877,11 +865,6 @@ ul.lm_tabs::before {
height: 8px;
}
- .flexlayout__border_button:hover .flexlayout__border_button_trailing,
- .flexlayout__border_button--selected .flexlayout__border_button_trailing {
- background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center;
- }
-
.flexlayout__border_toolbar_left {
position: absolute;
display: flex;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f155e64b5..a9474fe93 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,5 +1,6 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import * as GoldenLayout from '../../../client/goldenLayout';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -19,7 +20,8 @@ import { DragManager } from '../../util/DragManager';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { SettingsManager } from '../../util/SettingsManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
@@ -30,13 +32,11 @@ import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
-import React = require('react');
-import { SettingsManager } from '../../util/SettingsManager';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
- @observable public static Instance: CollectionDockingView | undefined;
+ @observable public static Instance: CollectionDockingView | undefined = undefined;
public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
return {
type: 'react-component',
@@ -62,14 +62,16 @@ export class CollectionDockingView extends CollectionSubView() {
}
private _goldenLayout: any = null;
static _highlightStyleSheet: any = addStyleSheet();
- constructor(props: SubCollectionViewProps) {
+
+ constructor(props: any) {
super(props);
- if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
+ makeObservable(this);
+ if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
//Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
DragManager.StartWindowDrag = this.StartOtherDrag;
- this.rootDoc.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit...
+ this.Document.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit...
}
/**
@@ -274,8 +276,8 @@ export class CollectionDockingView extends CollectionSubView() {
return true;
}
setupGoldenLayout = async () => {
- //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document)));
- const config = StrCast(this.props.Document.dockingConfig);
+ //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document)));
+ const config = StrCast(this._props.Document.dockingConfig);
if (config) {
const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
@@ -318,7 +320,7 @@ export class CollectionDockingView extends CollectionSubView() {
);
new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
this._reactionDisposer = reaction(
- () => StrCast(this.props.Document.dockingConfig),
+ () => StrCast(this._props.Document.dockingConfig),
config => {
if (!this._goldenLayout || this._ignoreStateChange !== config) {
// bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
@@ -328,7 +330,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
);
reaction(
- () => this.props.PanelWidth(),
+ () => this._props.PanelWidth(),
width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
{ fireImmediately: true }
);
@@ -375,14 +377,14 @@ export class CollectionDockingView extends CollectionSubView() {
.map(id => DocServer.GetCachedRefField(id))
.filter(f => f)
.map(f => f as Doc);
- const changesMade = this.props.Document.dockingConfig !== json;
+ const changesMade = this._props.Document.dockingConfig !== json;
if (changesMade) {
if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
this.layoutDoc.dockingConfig = json;
this.layoutDoc.data = new List<Doc>(docs);
} else {
- Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true);
- Doc.SetInPlace(this.rootDoc, 'data', new List<Doc>(docs), true);
+ Doc.SetInPlace(this.Document, 'dockingConfig', json, true);
+ Doc.SetInPlace(this.Document, 'data', new List<Doc>(docs), true);
}
}
this._flush?.end();
@@ -425,7 +427,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
public CaptureThumbnail() {
- const content = this.props.DocumentView?.()?.ContentDiv;
+ const content = this._props.DocumentView?.()?.ContentDiv;
if (content) {
const _width = Number(getComputedStyle(content).width.replace('px', ''));
const _height = Number(getComputedStyle(content).height.replace('px', ''));
@@ -465,6 +467,7 @@ export class CollectionDockingView extends CollectionSubView() {
});
const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
DashboardView.SetupDashboardTrails(copy);
+ DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed?
return DashboardView.openDashboard(copy);
}
@@ -472,16 +475,16 @@ export class CollectionDockingView extends CollectionSubView() {
stateChanged = () => {
this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
const json = JSON.stringify(this._goldenLayout.toConfig());
- const changesMade = this.props.Document.dockingConfig !== json;
+ const changesMade = this._props.Document.dockingConfig !== json;
return changesMade;
};
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);
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.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.rootDoc) tab.DashDoc.embedContainer = undefined;
+ 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.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
}
@@ -506,34 +509,37 @@ export class CollectionDockingView extends CollectionSubView() {
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(),
- _height: this.props.PanelHeight(),
+ _width: this._props.PanelWidth(),
+ _height: this._props.PanelHeight(),
_freeform_backgroundGrid: true,
_layout_fitWidth: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
- inheritParentAcls(this.rootDoc, docToAdd, false);
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+ inheritParentAcls(this.Document, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
});
- let addNewDoc = 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);
- inheritParentAcls(this.dataDoc, docToAdd, false);
- CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
- }
- });
+ 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'
+ );
stack.header?.controlsContainer
.find('.lm_close') //get the close icon
@@ -569,15 +575,15 @@ export class CollectionDockingView extends CollectionSubView() {
};
render() {
- const href = ImageCast(this.rootDoc.thumb)?.url?.href;
- return this.props.renderDepth > -1 ? (
+ const href = ImageCast(this.Document.thumb)?.url?.href;
+ return this._props.renderDepth > -1 ? (
<div>
{href ? (
<img
style={{ background: 'white', top: 0, position: 'absolute' }}
src={href} // + '?d=' + (new Date()).getTime()}
- width={this.props.PanelWidth()}
- height={this.props.PanelHeight()}
+ width={this._props.PanelWidth()}
+ height={this._props.PanelHeight()}
/>
) : (
<p>nested dashboards has no thumbnail</p>
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 06522b85e..8627ba650 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,13 +1,12 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+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 { DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { CompileScript } from '../../util/Scripting';
@@ -15,12 +14,10 @@ import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { CollectionStackingView } from './CollectionStackingView';
import './CollectionStackingView.scss';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
interface CMVFieldRowProps {
rows: () => number;
@@ -42,7 +39,12 @@ interface CMVFieldRowProps {
}
@observer
-export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> {
+export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVFieldRowProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable private _background = 'inherit';
@observable private _createEmbeddingSelected: boolean = false;
@observable private heading: string = '';
@@ -50,13 +52,13 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@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;
@@ -68,28 +70,28 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
this._dropDisposer?.();
if (ele) {
this._ele = ele;
- this.props.observeHeight(ele);
+ this._props.observeHeight(ele);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
};
@action
componentDidMount() {
- this.heading = this.props.headingObject?.heading || '';
- this.color = this.props.headingObject?.color || '#f1efeb';
- this.collapsed = this.props.headingObject?.collapsed || false;
+ this.heading = this._props.headingObject?.heading || '';
+ this.color = this._props.headingObject?.color || '#f1efeb';
+ this.collapsed = this._props.headingObject?.collapsed || false;
}
componentWillUnmount() {
- this.props.unobserveHeight(this._ele);
+ this._props.unobserveHeight(this._ele);
}
getTrueHeight = () => {
if (this.collapsed) {
- this.props.setDocHeight(this.heading, 20);
+ this._props.setDocHeight(this.heading, 20);
} else {
const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
- const transformScale = this.props.screenToLocalTransform().Scale;
+ const transformScale = this._props.screenToLocalTransform().Scale;
const trueHeight = rawHeight * transformScale;
- this.props.setDocHeight(this.heading, trueHeight);
+ this._props.setDocHeight(this.heading, trueHeight);
}
};
@@ -97,10 +99,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createEmbeddingSelected = false;
if (de.complete.docDragData) {
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const castedValue = this.getValue(this.heading);
const onLayoutDoc = this.onLayoutDoc(key);
- if (this.props.parent.onInternalDrop(e, de)) {
+ if (this._props.parent.onInternalDrop(e, de)) {
de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
}
return true;
@@ -119,15 +121,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
headingChanged = (value: string, shiftDown?: boolean) => {
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.parent.colHeaderData) {
- if (this.props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+ if (this._props.parent.colHeaderData) {
+ if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
return false;
}
}
- this.props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
+ this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
this._heading = castedValue.toString();
return true;
}
@@ -140,7 +142,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
this._color = color;
};
- pointerEnteredRow = action(() => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4'));
+ pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4'));
@action
pointerLeaveRow = () => {
@@ -152,24 +154,24 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
const onLayoutDoc = this.onLayoutDoc(key);
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = value;
- (onLayoutDoc ? newDoc : newDoc[DocData])[key] = 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)
+ (onLayoutDoc ? newDoc : newDoc[DocData])[key] = 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)
};
deleteRow = undoBatch(
action(() => {
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
- this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
- if (this.props.parent.colHeaderData && this.props.headingObject) {
- const index = this.props.parent.colHeaderData.indexOf(this.props.headingObject);
- this.props.parent.colHeaderData.splice(index, 1);
+ const key = this._props.pivotField;
+ this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
+ if (this._props.parent.colHeaderData && this._props.headingObject) {
+ const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
+ this._props.parent.colHeaderData.splice(index, 1);
}
})
);
@@ -182,8 +184,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
};
headerMove = (e: PointerEvent) => {
- const embedding = Doc.MakeEmbedding(this.props.Document);
- const key = this.props.pivotField;
+ const embedding = Doc.MakeEmbedding(this._props.Document);
+ const key = this._props.pivotField;
let value = this.getValue(this.heading);
value = typeof value === 'string' ? `"${value}"` : value;
const script = `return doc.${key} === ${value}`;
@@ -198,7 +200,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@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, e => !this._props.chromeHidden && this.collapseSection(e));
this._createEmbeddingSelected = false;
}
};
@@ -207,7 +209,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
* 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 => {
+ DocListCast(this._props.parent.Document.data).forEach(doc => {
if (Doc.Get(doc, key, true)) return true;
});
return false;
@@ -246,37 +248,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
toggleEmbedding = action(() => (this._createEmbeddingSelected = true));
toggleVisibility = () => (this._collapsed = !this.collapsed);
- renderMenu = () => {
- const selected = this._createEmbeddingSelected;
- return (
- <div className="collectionStackingView-optionPicker">
- <div className="optionOptions">
- <div className={'optionPicker' + (selected === true ? ' active' : '')} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
- Delete
- </div>
- </div>
- </div>
- );
- };
@action
textCallback = (char: string) => {
return 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))));
- const showChrome = !this.props.chromeHidden;
- const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
+ 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))));
+ const showChrome = !this._props.chromeHidden;
+ const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `;
return this.collapsed ? null : (
<div style={{ position: 'relative' }}>
- {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null}
+ {this._props.showHandle && this._props.parent._props.isContentActive() ? this._props.parent.columnDragger : null}
{showChrome ? (
<div
className="collectionStackingView-addDocumentButton"
style={
{
//width: style.columnWidth / style.numGroupColumns,
- //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`,
+ //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'} />
@@ -287,25 +277,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
ref={this._contRef}
style={{
padding: stackPad,
- minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined,
- width: this.props.parent.NodeWidth,
- gridGap: this.props.parent.gridGap,
- gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''),
+ minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
+ width: this._props.parent.NodeWidth,
+ gridGap: this._props.parent.gridGap,
+ gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''),
}}>
- {this.props.parent.children(this.props.docList)}
+ {this._props.parent.children(this._props.docList)}
</div>
</div>
);
}
@computed get headingView() {
- 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 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} />;
- return this.props.Document.miniHeaders ? (
+ return this._props.Document.miniHeaders ? (
<div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
- ) : !this.props.headingObject ? null : (
+ ) : !this._props.headingObject ? null : (
<div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
<div
className="collectionStackingView-sectionHeader-subCont"
@@ -338,11 +328,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
)}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}>
- <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}>
- <button className="collectionStackingView-sectionOptionButton">
- <FontAwesomeIcon icon="ellipsis-v" size="lg" />
- </button>
- </Flyout>
+ <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
+ <FontAwesomeIcon icon="trash" size="lg" />
+ </button>
</div>
)}
</div>
@@ -352,7 +340,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
render() {
const background = this._background;
return (
- <div className="collectionStackingView-masonrySection" style={{ width: this.props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
+ <div className="collectionStackingView-masonrySection" style={{ width: this._props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
{this.headingView}
{this.contentLayout}
</div>
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 6eeccc94e..5d46649b2 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
.collectionMenu-container {
display: flex;
@@ -19,4 +19,4 @@
display: flex;
flex-direction: row;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cf154be8d..443aa3f17 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,43 +1,26 @@
-import React = require('react');
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import * as React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx';
+import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { ColorState } from 'react-color';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Document } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
-import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu } from '../AntimodeMenu';
import { EditableView } from '../EditableView';
-import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
-import { LightboxView } from '../LightboxView';
import { MainView } from '../MainView';
-import { media_state } from '../nodes/AudioBox';
-import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView, DocumentViewInternal, OpenWhereMod } from '../nodes/DocumentView';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionLinearView } from './collectionLinear';
import './CollectionMenu.scss';
@@ -50,23 +33,24 @@ interface CollectionMenuProps {
export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@observable static Instance: CollectionMenu;
- @observable SelectedCollection: DocumentView | undefined;
+ @observable SelectedCollection: DocumentView | undefined = undefined;
@observable FieldKey: string;
private _docBtnRef = React.createRef<HTMLDivElement>();
constructor(props: any) {
super(props);
+ makeObservable(this);
this.FieldKey = '';
- runInAction(() => (CollectionMenu.Instance = this));
+ CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
- runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true)));
+ this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true);
this.jumpTo(300, 300);
}
componentDidMount() {
reaction(
- () => SelectionManager.Views().length && SelectionManager.Views()[0],
+ () => SelectionManager.Views.lastElement(),
view => view && this.SetSelection(view)
);
}
@@ -86,19 +70,19 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@action
toggleTopBar = () => {
- if (SettingsManager.headerBarHeight > 0) {
- SettingsManager.headerBarHeight = 0;
+ if (SettingsManager.Instance.headerBarHeight > 0) {
+ SettingsManager.Instance.headerBarHeight = 0;
} else {
- SettingsManager.headerBarHeight = 60;
+ SettingsManager.Instance.headerBarHeight = 60;
}
};
@action
toggleProperties = () => {
if (MainView.Instance.propertiesWidth() > 0) {
- SettingsManager.propertiesWidth = 0;
+ SettingsManager.Instance.propertiesWidth = 0;
} else {
- SettingsManager.propertiesWidth = 300;
+ SettingsManager.Instance.propertiesWidth = 300;
}
};
@@ -111,15 +95,13 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@computed get contMenuButtons() {
const selDoc = Doc.MyContextMenuBtns;
return !(selDoc instanceof Doc) ? null : (
- <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}>
+ <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}>
<CollectionLinearView
Document={selDoc}
- DataDoc={undefined}
fieldKey="data"
dropAction="embed"
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
isContentActive={returnTrue}
@@ -132,8 +114,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
pinToPres={emptyFunction}
removeDocument={returnFalse}
ScreenToLocalTransform={this.buttonBarXf}
- PanelWidth={this.props.panelWidth}
- PanelHeight={this.props.panelHeight}
+ PanelWidth={this._props.panelWidth}
+ PanelHeight={this._props.panelHeight}
renderDepth={0}
focus={emptyFunction}
whenChildContentsActiveChanged={emptyFunction}
@@ -146,10 +128,10 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
render() {
- const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
- const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
- const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
- const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
+ const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
+ const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+ const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
const hardCodedButtons = (
<div className={`hardCodedButtons`}>
@@ -158,7 +140,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
type={Type.PRIM}
color={SettingsManager.userColor}
onClick={this.toggleTopBar}
- toggleStatus={SettingsManager.headerBarHeight > 0}
+ toggleStatus={SettingsManager.Instance.headerBarHeight > 0}
icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
tooltip={headerTitle}
/>
@@ -167,14 +149,13 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
type={Type.PRIM}
color={SettingsManager.userColor}
onClick={this.toggleProperties}
- toggleStatus={SettingsManager.propertiesWidth > 0}
+ toggleStatus={SettingsManager.Instance.propertiesWidth > 0}
icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
tooltip={propTitle}
/>
</div>
);
- // NEW BUTTONS
//dash col linear view buttons
const contMenuButtons = (
<div
@@ -189,21 +170,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
);
return contMenuButtons;
-
- // const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
- // <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
- // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- // </button>
- // </Tooltip>;
-
- // //OLD BUTTONS
- // return this.getElement(!this.SelectedCollection ? [/*button*/] :
- // [<CollectionViewBaseChrome key="chrome"
- // docView={this.SelectedCollection}
- // fieldKey={this.SelectedCollection.LayoutFieldKey}
- // type={StrCast(this.SelectedCollection?.props.Document._type_collection, CollectionViewType.Invalid) as CollectionViewType} />,
- // prop,
- // /*button*/]);
}
}
@@ -220,7 +186,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get document() {
- return this.props.docView?.props.Document;
+ return this.props.docView?.Document;
}
get target() {
return this.document;
@@ -228,7 +194,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
_templateCommand = {
params: ['target', 'source'],
title: 'item view',
- script: 'self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])',
+ script: 'this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])',
immediate: undoBatch((source: Doc[]) => {
let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text;
try {
@@ -250,24 +216,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
_narrativeCommand = {
params: ['target', 'source'],
title: 'child click view',
- script: 'self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])',
+ script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])',
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
params: ['target', 'source'],
title: 'set content',
- script: 'getProto(self.target).data = copyField(self.source);',
+ script: 'getProto(this.target).data = copyField(this.source);',
immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))),
initialize: emptyFunction,
};
_onClickCommand = {
params: ['target', 'proxy'],
title: 'copy onClick',
- script: `{ if (self.proxy?.[0]) {
- getProto(self.proxy[0]).onClick = copyField(self.target.onClick);
- getProto(self.proxy[0]).target = self.target.target;
- getProto(self.proxy[0]).source = copyField(self.target.source);
+ script: `{ if (this.proxy?.[0]) {
+ getProto(this.proxy[0]).onClick = copyField(this.target.onClick);
+ getProto(this.proxy[0]).target = this.target.target;
+ getProto(this.proxy[0]).source = copyField(this.target.source);
}}`,
immediate: undoBatch((source: Doc[]) => {}),
initialize: emptyFunction,
@@ -275,7 +241,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
_viewCommand = {
params: ['target'],
title: 'bookmark view',
- script: "self.target._freeform_panX = self['target-freeform_panX']; self.target._freeform_panY = self['target-freeform_panY']; self.target._freeform_scale = self['target_freeform_scale']; gotoFrame(self.target, self['target-currentFrame']);",
+ 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[]) => {
this.target._freeform_panX = 0;
this.target._freeform_panY = 0;
@@ -292,22 +258,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
_clusterCommand = {
params: ['target'],
title: 'fit content',
- script: 'self.target._freeform_fitContentsToBox = !self.target._freeform_fitContentsToBox;',
+ script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;',
immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
initialize: emptyFunction,
};
_fitContentCommand = {
params: ['target'],
title: 'toggle clusters',
- script: 'self.target._freeform_useClusters = !self.target._freeform_useClusters;',
+ script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;',
immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
initialize: emptyFunction,
};
_saveFilterCommand = {
params: ['target'],
title: 'save filter',
- script: `self.target._childFilters = compareLists(self['target-childFilters'],self.target._childFilters) ? undefined : copyField(self['target-childFilters']);
- self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
+ 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[]) => {
this.target._childFilters = undefined;
this.target._searchFilterDocs = undefined;
@@ -390,57 +356,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
this.document._facetWidth = 0;
};
- @computed get subChrome() {
- switch (
- this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type // bcz: ARgh! hack to get menu for tree view outline items
- ) {
- default:
- return this.otherSubChrome;
- case CollectionViewType.Invalid:
- case CollectionViewType.Freeform:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />;
- case CollectionViewType.Stacking:
- return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.NoteTaking:
- return <CollectionNoteTakingViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Schema:
- return <CollectionSchemaViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Tree:
- return <CollectionTreeViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Masonry:
- return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Carousel:
- case CollectionViewType.Carousel3D:
- return <Collection3DCarouselViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Grid:
- return <CollectionGridViewChrome key="collchrome" {...this.props} />;
- case CollectionViewType.Docking:
- return <CollectionDockingChrome key="collchrome" {...this.props} />;
- }
- }
-
- @computed get otherSubChrome() {
- const docType = this.props.docView.Document.type;
- switch (docType) {
- default:
- return null;
- case DocumentType.IMG:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- case DocumentType.PDF:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- case DocumentType.INK:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- case DocumentType.WEB:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- case DocumentType.VID:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- case DocumentType.RTF:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />;
- case DocumentType.MAP:
- return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
- }
- }
-
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
@@ -517,593 +432,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
</div>
);
}
-
- @computed get viewModes() {
- const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
- const isPres: boolean = this.document && this.document.type === DocumentType.PRES;
- return isPres ? null : (
- <div className="collectionViewBaseChrome-viewModes">
- <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
- <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}>
- <button className={'antimodeMenu-button'}>
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- <select className="collectionViewBaseChrome-viewPicker" onPointerDown={stopPropagation} onChange={this.viewChanged} value={StrCast(this.props.type)}>
- {Object.values(CollectionViewType)
- .filter(type => !excludedViewTypes.includes(type))
- .map(type => (
- <option key={Utils.GenerateGuid()} className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value={type}>
- {type[0].toUpperCase() + type.substring(1)}
- </option>
- ))}
- </select>
- </div>
- </Tooltip>
- </div>
- );
- }
-
- @computed get selectedDocumentView() {
- return SelectionManager.Views().lastElement();
- }
- @computed get selectedDoc() {
- return SelectionManager.Docs().lastElement();
- }
- @computed get notACollection() {
- if (this.selectedDoc) {
- const layoutField = Doc.LayoutField(this.selectedDoc);
- return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView'));
- } else return false;
- }
-
- @undoBatch
- @action
- startRecording = () => {
- const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: media_state.PendingRecording });
- CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- };
-
- @computed
- get recordButton() {
- const targetDoc = this.selectedDoc;
- return (
- <Tooltip key="record" title={<div className="dash-tooltip">{'Capture screen'}</div>} placement="top">
- <button className="antimodeMenu-button" onClick={e => this.startRecording()}>
- <div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}></div>
- </div>
- </button>
- </Tooltip>
- );
- }
-
- @undoBatch
- onEmbed = () => {
- if (this.selectedDoc && this.selectedDocumentView) {
- // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
- // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
- // copy.y = NumCast(this.selectedDoc.y) + 30;
- // this.selectedDocumentView.props.addDocument?.(copy);
- const embedding = Doc.MakeEmbedding(this.selectedDoc);
- embedding.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
- embedding.y = NumCast(this.selectedDoc.y) + 30;
- this.selectedDocumentView.props.addDocument?.(embedding);
- }
- };
- onEmbedButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onEmbedButtonMoved, emptyFunction, emptyFunction);
- };
-
- @undoBatch
- onEmbedButtonMoved = (e: PointerEvent) => {
- const contentDiv = this.selectedDocumentView?.ContentDiv;
- if (contentDiv && this.selectedDoc) {
- const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
- const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
- dragData.defaultDropAction = 'embed';
- dragData.canEmbed = true;
- DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, {
- offsetX: offset[0],
- offsetY: offset[1],
- hideSource: false,
- });
- return true;
- }
- return false;
- };
-
- @computed
- get embedButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc || targetDoc.type === DocumentType.PRES ? null : (
- <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an embedding'}</div>} placement="top">
- <button className="antimodeMenu-button" onPointerDown={this.onEmbedButtonDown} onClick={this.onEmbed} style={{ cursor: 'drag' }}>
- <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" />
- </button>
- </Tooltip>
- );
- }
-
- @computed get lightboxButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{'View in Lightbox'}</div>} placement="top">
- <button
- className="antimodeMenu-button"
- onPointerDown={() => {
- const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
- LightboxView.Instance.SetLightboxDoc(targetDoc, undefined, docs);
- }}>
- <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" />
- </button>
- </Tooltip>
- );
- }
-
- @computed get toggleOverlayButton() {
- return (
- <>
- <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom">
- <button
- className={'antimodeMenu-button'}
- key="float"
- style={{
- backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
- pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'none' : undefined,
- color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
- }}
- onClick={undoBatch(() => this.props.docView.CollectionFreeFormDocumentView?.float())}>
- <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
- </button>
- </Tooltip>
- </>
- );
- }
-
render() {
return (
<div className="collectionMenu-cont">
<div className="collectionMenu">
- <div className="collectionViewBaseChrome">
- {this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes}
- <div className="collectionMenu-divider" key="divider1"></div>
- {this.embedButton}
- {/* {this.pinButton} */}
- {this.toggleOverlayButton}
- <div className="collectionMenu-divider" key="divider2"></div>
- {this.subChrome}
- <div className="collectionMenu-divider" key="divider3"></div>
- {this.lightboxButton}
- {this.recordButton}
- {!this._buttonizableCommands ? null : this.templateChrome}
- </div>
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionDockingChrome extends React.Component<CollectionViewMenuProps> {
- render() {
- return null;
- }
-}
-
-@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean; isDoc?: boolean }> {
- public static Instance: CollectionFreeFormViewChrome;
- constructor(props: any) {
- super(props);
- CollectionFreeFormViewChrome.Instance = this;
- }
- get document() {
- return this.props.docView.props.Document;
- }
- @computed get dataField() {
- return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '_annotations' : '')];
- }
- @computed get childDocs() {
- return DocListCast(this.dataField);
- }
- @computed get selectedDocumentView() {
- return SelectionManager.Views().lastElement();
- }
- @computed get selectedDoc() {
- return SelectionManager.Docs().lastElement();
- }
- @computed get isText() {
- return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
- }
-
- public static gotoKeyFrame(doc: Doc, newFrame: number) {
- if (doc) {
- const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
- const currentFrame = Cast(doc._currentFrame, 'number', null);
- if (currentFrame === undefined) {
- doc._currentFrame = 0;
- CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
- }
- CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
- doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
- }
- }
-
- _keyTimer: NodeJS.Timeout | undefined;
- @undoBatch
- @action
- nextKeyframe = (): void => {
- const currentFrame = Cast(this.document._currentFrame, 'number', null);
- if (currentFrame === undefined) {
- this.document._currentFrame = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
- }
- this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0);
- this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
- this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
- };
- @undoBatch
- @action
- prevKeyframe = (): void => {
- const currentFrame = Cast(this.document._currentFrame, 'number', null);
- if (currentFrame === undefined) {
- this.document._currentFrame = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
- }
- this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000);
- this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
- };
-
- private _palette = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', ''];
- private _width = ['1', '5', '10', '100'];
- private _dotsize = [10, 20, 30, 40];
- private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O'];
- private _head = ['', '', '', '', 'arrow', '', ''];
- private _end = ['', '', '', 'arrow', 'arrow', '', ''];
- private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'] as GestureUtils.Gestures[];
- private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle'];
- private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle'];
- @observable _selectedPrimitive = this._shapePrims.length;
- @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
- @observable _colorBtn = false;
- @observable _widthBtn = false;
- @observable _fillBtn = false;
-
- @action clearKeepPrimitiveMode() {
- this._selectedPrimitive = this._shapePrims.length;
- }
- @action primCreated() {
- if (!this._keepPrimitiveMode) {
- //get out of ink mode after each stroke=
- if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
- Doc.ActiveTool = InkTool.None;
- this._selectedPrimitive = this._shapePrims.length;
- SetActiveArrowStart('none');
- SetActiveArrowEnd('none');
- }
- }
-
- @action
- changeColor = (color: string, type: string) => {
- const col: ColorState = {
- hex: color,
- hsl: { a: 0, h: 0, s: 0, l: 0, source: '' },
- hsv: { a: 0, h: 0, s: 0, v: 0, source: '' },
- rgb: { a: 0, r: 0, b: 0, g: 0, source: '' },
- oldHue: 0,
- source: '',
- };
- if (type === 'color') {
- SetActiveInkColor(Utils.colorString(col));
- } else if (type === 'fill') {
- SetActiveFillColor(Utils.colorString(col));
- }
- };
-
- @action
- editProperties = (value: any, field: string) => {
- SelectionManager.Views().forEach(
- action((element: DocumentView) => {
- const doc = Document(element.rootDoc);
- if (doc.type === DocumentType.INK) {
- switch (field) {
- case 'width':
- doc.stroke_width = Number(value);
- break;
- case 'color':
- doc.color = String(value);
- break;
- case 'fill':
- doc.fillColor = String(value);
- break;
- case 'dash':
- doc.stroke_dash = value;
- }
- }
- })
- );
- };
-
- @computed get drawButtons() {
- const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => {
- this._keepPrimitiveMode = keep;
- // these are for shapes
- if (this._selectedPrimitive !== i) {
- this._selectedPrimitive = i;
- if (this._title[i] === 'highlighter') {
- Doc.ActiveTool = InkTool.Highlighter;
- GestureOverlay.Instance.SavedColor = ActiveInkColor();
- SetActiveInkColor('rgba(245, 230, 95, 0.75)');
- } else {
- Doc.ActiveTool = InkTool.Pen;
- }
- SetActiveArrowStart(this._head[i]);
- SetActiveArrowEnd(this._end[i]);
- SetActiveBezierApprox('300');
-
- if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = this._shapePrims[i];
- } else {
- this._selectedPrimitive = this._shapePrims.length;
- Doc.ActiveTool = InkTool.None;
- SetActiveArrowStart('');
- SetActiveArrowEnd('');
- if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = undefined;
- SetActiveBezierApprox('0');
- }
- e.stopPropagation();
- });
- return (
- <div className="btn-draw" key="draw">
- {this._draw.map((icon, i) => (
- <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={e => func(e, i, false)} onDoubleClick={e => func(e, i, true)} style={{ backgroundColor: i === this._selectedPrimitive ? '525252' : '', fontSize: '20' }}>
- <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
- </button>
- </Tooltip>
- ))}
- </div>
- );
- }
-
- toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps['icon'], ele: JSX.Element | null) => {
- return (
- <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom">
- <button className="antimodeMenu-button" key={key} onPointerDown={action(e => setter())} style={{ backgroundColor: value ? '121212' : '' }}>
- <FontAwesomeIcon icon={icon} size="lg" />
- {ele}
- </button>
- </Tooltip>
- );
- };
-
- @computed get widthPicker() {
- const widthPicker = this.toggleButton('stroke width', this._widthBtn, () => (this._widthBtn = !this._widthBtn), 'bars', null);
- return !this._widthBtn ? (
- widthPicker
- ) : (
- <div className="btn2-group" key="width">
- {widthPicker}
- {this._width.map((wid, i) => (
- <Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom">
- <button
- className="antimodeMenu-button"
- key={wid}
- onPointerDown={action(() => {
- SetActiveInkWidth(wid);
- this._widthBtn = false;
- this.editProperties(wid, 'width');
- })}
- style={{ backgroundColor: this._widthBtn ? '121212' : '', zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: 'center' }}>
- •
- </button>
- </Tooltip>
- ))}
- </div>
- );
- }
-
- @computed get colorPicker() {
- const colorPicker = this.toggleButton('stroke color', this._colorBtn, () => (this._colorBtn = !this._colorBtn), 'pen-nib', <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? '121212' }} />);
- return !this._colorBtn ? (
- colorPicker
- ) : (
- <div className="btn-group" key="color">
- {colorPicker}
- {this._palette.map(color => (
- <button
- className="antimodeMenu-button"
- key={color}
- onPointerDown={action(() => {
- this.changeColor(color, 'color');
- this._colorBtn = false;
- this.editProperties(color, 'color');
- })}
- style={{ backgroundColor: this._colorBtn ? '121212' : '', zIndex: 1001 }}>
- {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
- <div className="color-previewII" style={{ backgroundColor: color }}>
- {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
- </div>
- </button>
- ))}
- </div>
- );
- }
- @computed get fillPicker() {
- const fillPicker = this.toggleButton('shape fill color', this._fillBtn, () => (this._fillBtn = !this._fillBtn), 'fill-drip', <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? '121212' }} />);
- return !this._fillBtn ? (
- fillPicker
- ) : (
- <div className="btn-group" key="fill">
- {fillPicker}
- {this._palette.map(color => (
- <button
- className="antimodeMenu-button"
- key={color}
- onPointerDown={action(() => {
- this.changeColor(color, 'fill');
- this._fillBtn = false;
- this.editProperties(color, 'fill');
- })}
- style={{ backgroundColor: this._fillBtn ? '121212' : '', zIndex: 1001 }}>
- <div className="color-previewII" style={{ backgroundColor: color }}>
- {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
- </div>
- </button>
- ))}
- </div>
- );
- }
-
- render() {
- return !this.props.docView.layoutDoc ? null : (
- <div className="collectionFreeFormMenu-cont">
- <RichTextMenu key="rich" />
- {!this.isText ? (
- <>
- {this.drawButtons}
- {this.widthPicker}
- {this.colorPicker}
- {this.fillPicker}
- {Doc.noviceMode || this.props.isDoc ? null : (
- <>
- <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
- <div className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
- </div>
- </Tooltip>
- <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom">
- <div
- className="numKeyframe"
- style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
- onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))}>
- {NumCast(this.document._currentFrame)}
- </div>
- </Tooltip>
- <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
- <div className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
- </div>
- </Tooltip>
- </>
- )}
- </>
- ) : null}
- {!this.selectedDocumentView?.ComponentView?.menuControls ? null : this.selectedDocumentView?.ComponentView?.menuControls?.()}
- </div>
- );
- }
-}
-@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewMenuProps> {
- @observable private _currentKey: string = '';
- @observable private suggestions: string[] = [];
-
- get document() {
- return this.props.docView.props.Document;
- }
-
- @computed private get descending() {
- return StrCast(this.document._columnsSort) === 'descending';
- }
- @computed get pivotField() {
- return StrCast(this.document._pivotField);
- }
-
- getKeySuggestions = async (value: string): Promise<string[]> => {
- const val = value.toLowerCase();
- const docs = DocListCast(this.document[this.props.fieldKey]);
-
- if (Doc.noviceMode) {
- 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);
- }
- 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);
- }
- };
-
- @action
- onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
- this._currentKey = newValue;
- };
-
- getSuggestionValue = (suggestion: string) => suggestion;
-
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- };
-
- onSuggestionFetch = async ({ value }: { value: string }) => {
- const sugg = await this.getKeySuggestions(value);
- runInAction(() => {
- this.suggestions = sugg;
- });
- };
-
- @action
- onSuggestionClear = () => {
- this.suggestions = [];
- };
-
- @action
- setValue = (value: string) => {
- this.document._pivotField = value;
- return true;
- };
-
- @action toggleSort = () => {
- this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending';
- };
- @action resetValue = () => {
- this._currentKey = this.pivotField;
- };
-
- render() {
- const doctype = this.props.docView.Document.type;
- const isPres: boolean = doctype === DocumentType.PRES;
- return isPres ? null : (
- <div className="collectionStackingViewChrome-cont">
- <div className="collectionStackingViewChrome-pivotField-cont">
- <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div>
- <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}>
- <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
- </div>
- <div className="collectionStackingViewChrome-pivotField">
- <EditableView
- GetValue={() => this.pivotField}
- autosuggestProps={{
- resetValue: this.resetValue,
- value: this._currentKey,
- onChange: this.onKeyChange,
- autosuggestProps: {
- inputProps: {
- value: this._currentKey,
- onChange: this.onKeyChange,
- },
- getSuggestionValue: this.getSuggestionValue,
- suggestions: this.suggestions,
- alwaysRenderSuggestions: true,
- renderSuggestion: this.renderSuggestion,
- onSuggestionsFetchRequested: this.onSuggestionFetch,
- onSuggestionsClearRequested: this.onSuggestionClear,
- },
- }}
- oneLine
- SetValue={this.setValue}
- contents={this.pivotField ? this.pivotField : 'N/A'}
- />
- </div>
+ <div className="collectionViewBaseChrome">{!this._buttonizableCommands ? null : this.templateChrome}</div>
</div>
</div>
);
@@ -1209,6 +542,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
autosuggestProps: {
inputProps: {
value: this._currentKey,
+ // @ts-ignore
onChange: this.onKeyChange,
},
getSuggestionValue: this.getSuggestionValue,
@@ -1230,123 +564,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
}
}
-@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
- // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
- get document() {
- return this.props.docView.props.Document;
- }
-
- @undoBatch
- togglePreview = () => {
- const dividerWidth = 4;
- const borderWidth = 0;
- const panelWidth = this.props.docView.props.PanelWidth();
- const previewWidth = NumCast(this.document.schema_previewWidth);
- const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
- this.document.schema_previewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
- };
-
- @undoBatch
- @action
- toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []);
- if (textwrappedRows.length) {
- this.document.textwrappedSchemaRows = new List<string>([]);
- } else {
- const docs = DocListCast(this.document[this.props.fieldKey]);
- const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.document.textwrappedSchemaRows = new List<string>(allRows);
- }
- };
-
- render() {
- const previewWidth = NumCast(this.document.schema_previewWidth);
- const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0;
-
- return (
- <div className="collectionSchemaViewChrome-cont">
- <div className="collectionSchemaViewChrome-toggle">
- <div className="collectionSchemaViewChrome-label">Show Preview: </div>
- <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
- <div className={'collectionSchemaViewChrome-togglerButton' + (previewWidth !== 0 ? ' on' : ' off')}>{previewWidth !== 0 ? 'on' : 'off'}</div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewMenuProps> {
- get document() {
- return this.props.docView.props.Document;
- }
- get sortAscending() {
- return this.document[this.props.fieldKey + '-sortAscending'];
- }
- set sortAscending(value) {
- this.document[this.props.fieldKey + '-sortAscending'] = value;
- }
- @computed private get ascending() {
- return Cast(this.sortAscending, 'boolean', null);
- }
-
- @action toggleSort = () => {
- if (this.sortAscending) this.sortAscending = undefined;
- else if (this.sortAscending === undefined) this.sortAscending = false;
- else this.sortAscending = true;
- };
-
- render() {
- return (
- <div className="collectionTreeViewChrome-cont">
- <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
- <div className="collectionTreeViewChrome-sortLabel">Sort</div>
- <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? '90' : this.ascending ? '180' : '0'}deg)` }}>
- <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
- </div>
- </button>
- </div>
- );
- }
-}
-
-// Enter scroll speed for 3D Carousel
-@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionViewMenuProps> {
- get document() {
- return this.props.docView.props.Document;
- }
- @computed get scrollSpeed() {
- return this.document._autoScrollSpeed;
- }
-
- @action
- setValue = (value: string) => {
- const numValue = Number(StrCast(value));
- if (numValue > 0) {
- this.document._autoScrollSpeed = numValue;
- return true;
- }
- return false;
- };
-
- render() {
- return (
- <div className="collection3DCarouselViewChrome-cont">
- <div className="collection3DCarouselViewChrome-scrollSpeed-cont">
- {/* {FormattedTextBox.Focused ? <RichTextMenu /> : null} */}
- <div className="collectionStackingViewChrome-scrollSpeed-label">AUTOSCROLL SPEED:</div>
- <div className="collection3DCarouselViewChrome-scrollSpeed">
- <EditableView GetValue={() => StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
- </div>
- </div>
- </div>
- );
- }
-}
-
/**
* Chrome for grid view.
*/
@@ -1361,13 +578,18 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
return this.props.docView.props.Document;
}
+ @computed get panelWidth() {
+ return this.props.docView.props.PanelWidth();
+ }
+
componentDidMount() {
runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
// listener to reduce text on chrome resize (panel resize)
- this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
- runInAction(() => (this.resize = newValue < 700));
- });
+ this.resizeListenerDisposer = reaction(
+ () => this.panelWidth,
+ newValue => (this.resize = newValue < 700)
+ );
}
componentWillUnmount() {
@@ -1492,12 +714,6 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
<input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
<input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
</span>
- {/* <span className="grid-control">
- <span className="grid-icon">
- <FontAwesomeIcon icon="text-height" size="1x" />
- </span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
- </span> */}
<span className="grid-control" style={{ width: this.resize ? '12%' : '20%' }}>
<input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
<label className="flexLabel">{this.resize ? 'Coll' : 'Collisions'}</label>
@@ -1526,6 +742,3 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
);
}
}
-ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
- CollectionFreeFormViewChrome.gotoKeyFrame(doc, newFrame);
-});
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index be1800d81..91a82d40f 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.collectionNoteTakingView-DocumentButtons {
display: none;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index ac916fef3..f9d258490 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,5 @@
-import React = require('react');
-import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import * as React from 'react';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Field, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -44,8 +43,13 @@ export class CollectionNoteTakingView extends CollectionSubView() {
notetakingCategoryField = 'NotetakingCategory';
public DividerWidth = 16;
@observable docsDraggedRowCol: number[] = [];
- @observable _cursor: CursorProperty = 'grab';
@observable _scroll = 0;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get chromeHidden() {
return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false;
}
@@ -221,7 +225,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
// getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
getDisplayDoc(doc: Doc, width: () => number) {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.TemplateDataDocument;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
@@ -229,8 +233,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
<DocumentView
ref={r => (dref = r || undefined)}
Document={doc}
+ TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
pointerEvents={this.blockPointerEventsWhenDragging}
- DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
@@ -257,9 +261,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
ScreenToLocalTransform={noteTakingDocTransform}
focus={this.focusDocument}
childFilters={this.childDocFilters}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideTitle={this.props.childHideTitle?.()}
+ hideDecorationTitle={this.props.childHideDecorationTitle}
+ hideResizeHandles={this.props.childHideResizeHandles}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
addDocument={this.props.addDocument}
@@ -297,7 +300,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.TemplateDataDocument;
const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
@@ -336,7 +339,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// onPointerMove is used to preview where a document will drop in a column once a drag is complete.
@action
onPointerMove = (force: boolean, ex: number, ey: number) => {
- if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || this.isContentActive()) {
+ if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || SnappingManager.CanEmbed) {
// get the current docs for the column based on the mouse's x coordinate
const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
const colDocs = this.getDocsFromXCoord(xCoord);
@@ -407,11 +410,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.ctrlKey || docView.rootDoc._createDocOnCR) && ['Enter'].includes(e.key)) {
+ if (docView && (e.ctrlKey || docView.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
e.stopPropagation?.();
- const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ const newDoc = Doc.MakeCopy(docView.Document, true);
Doc.GetProto(newDoc).text = undefined;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
};
@@ -419,7 +422,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// onInternalDrop is used when dragging and dropping a document within the view, such as dragging
// a document to a new column or changing its order within the column.
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
if (super.onInternalDrop(e, de)) {
@@ -458,7 +460,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@undoBatch
internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) {
const dropCreator = annoDragData.dropDocCreator;
- annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.rootDoc;
+ annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.Document;
return true;
}
@@ -509,7 +511,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
this.props.setHeight?.(height);
@@ -526,7 +528,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
Document={this.props.Document}
- DataDoc={this.props.DataDoc}
+ TemplateDataDocument={this.props.TemplateDataDocument}
resizeColumns={this.resizeColumns}
renderChildren={this.children}
numGroupColumns={this.numGroupColumns}
@@ -635,7 +637,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
key="notes"
style={{
overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor),
pointerEvents: this.backgroundEvents,
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 52cc21903..bb01a1782 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace } from 'mobx';
import { observer } from 'mobx-react';
@@ -25,7 +25,7 @@ import './CollectionNoteTakingView.scss';
interface CSVFieldColumnProps {
Document: Doc;
- DataDoc: Opt<Doc>;
+ TemplateDataDocument: Opt<Doc>;
docList: Doc[];
heading: string;
pivotField: string;
@@ -121,7 +121,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
return false;
};
- @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
+ @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
@action pointerLeave = () => (this._background = 'inherit');
@undoBatch
addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@@ -134,7 +134,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
const colValue = this.getValue(this.props.heading);
newDoc[key] = colValue;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
return this.props.addDocument?.(newDoc) || false;
};
@@ -157,14 +157,14 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
- const dataDoc = this.props.DataDoc || this.props.Document;
+ const dataDoc = this.props.TemplateDataDocument || this.props.Document;
const pivotValue = this.getValue(this.props.heading);
DocUtils.addDocumentCreatorMenuItems(
doc => {
const key = this.props.pivotField;
doc[key] = this.getValue(this.props.heading);
- FormattedTextBox.SelectOnLoad = doc[Id];
+ FormattedTextBox.SetSelectOnLoad(doc);
return this.props.addDocument?.(doc);
},
this.props.addDocument,
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index abb12a8ab..170b1f1ec 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { ScriptField } from '../../../fields/ScriptField';
@@ -12,13 +12,18 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import './CollectionPileView.scss';
import { CollectionSubView } from './CollectionSubView';
-import React = require('react');
+import * as React from 'react';
@observer
export class CollectionPileView extends CollectionSubView() {
_originalChrome: any = '';
_disposers: { [name: string]: IReactionDisposer } = {};
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) {
this.Document._freeform_pileEngine = computePassLayout.name;
@@ -43,7 +48,7 @@ export class CollectionPileView extends CollectionSubView() {
removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
(doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
- if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc);
+ if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.()._props.removeDocument?.(this.Document);
return ret;
};
@@ -78,12 +83,12 @@ export class CollectionPileView extends CollectionSubView() {
toggleStarburst = action(() => {
this.layoutDoc._freeform_scale = undefined;
if (this.layoutEngine() === computeStarburstLayout.name) {
- if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) {
- this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width);
+ if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) {
+ this.Document._starburstDiameter = NumCast(this.layoutDoc._width);
}
const defaultSize = 110;
- this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
- this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+ this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+ this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
@@ -91,9 +96,9 @@ export class CollectionPileView extends CollectionSubView() {
this.layoutDoc._freeform_panY = -10;
this.props.Document._freeform_pileEngine = computePassLayout.name;
} else {
- const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
- this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
- this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
+ const defaultSize = NumCast(this.Document._starburstDiameter, 400);
+ this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+ this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index a19d8e696..0ced3f9e3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.collectionStackedTimeline-timelineContainer {
height: 100%;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 3351ca48e..fb8bc4da2 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,7 @@
-import React = require('react');
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+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 { Doc, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -17,16 +17,16 @@ import { DragManager } from '../../util/DragManager';
import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { AudioWaveform } from '../AudioWaveform';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
import { LightboxView } from '../LightboxView';
+import { AudioWaveform } from '../nodes/audio/AudioWaveform';
import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
import { LabelBox } from '../nodes/LabelBox';
import { VideoBox } from '../nodes/VideoBox';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import './CollectionStackedTimeline.scss';
export type CollectionStackedTimelineProps = {
@@ -35,6 +35,7 @@ export type CollectionStackedTimelineProps = {
playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
playing: () => boolean;
+ thumbnails?: () => string[];
setTime: (time: number) => void;
startTag: string;
endTag: string;
@@ -43,7 +44,6 @@ export type CollectionStackedTimelineProps = {
rawDuration: number;
dataFieldKey: string;
fieldKey: string;
- thumbnails?: () => string[];
};
// trimming state: shows full clip, current trim bounds, or not trimming
@@ -57,6 +57,10 @@ export enum TrimScope {
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
@observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
@observable public static CurrentlyPlaying: DocumentView[];
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
static LabelScript: ScriptField;
static LabelPlayScript: ScriptField;
@@ -64,7 +68,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
private _markerStart: number = 0;
- @observable _markerEnd: number | undefined;
+ @observable _markerEnd: number | undefined = undefined;
@observable _trimming: number = TrimScope.None;
@observable _trimStart: number = 0; // trim controls start pos
@observable _trimEnd: number = 0; // trim controls end pos
@@ -74,14 +78,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@observable _hoverTime: number = 0;
- @observable _thumbnail: string | undefined;
+ @observable _thumbnail: string | undefined = undefined;
// ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore
get minTrimLength() {
return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
}
@computed get thumbnails() {
- return this.props.thumbnails?.();
+ return this._props.thumbnails?.();
}
@computed get trimStart() {
@@ -101,7 +105,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return this.clipEnd - this.clipStart;
}
@computed get clipEnd() {
- return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
+ return this.IsTrimming === TrimScope.All ? this._props.rawDuration : NumCast(this.layoutDoc.clipEnd, this._props.rawDuration);
}
@computed get currentTime() {
@@ -116,11 +120,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
document.addEventListener('keydown', this.keyEvents, true);
}
- @action
componentWillUnmount() {
document.removeEventListener('keydown', this.keyEvents, true);
if (CollectionStackedTimeline.SelectingRegion === this) {
- CollectionStackedTimeline.SelectingRegion = undefined;
+ runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined));
}
}
@@ -150,8 +153,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._zoomFactor = zoom;
}
- anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
- anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null);
+ anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag]));
+ anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
// converts screen pixel offset to time
toTimeline = (screen_delta: number, width: number) => {
@@ -178,13 +181,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
if (
// need to include range inputs because after dragging video time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isContentActive()
+ this._props.isContentActive()
) {
// if shift pressed scrub 1 second otherwise 1/10th
const jump = e.shiftKey ? 1 : 0.1;
switch (e.key) {
case ' ':
- this.props.playing() ? this.props.Pause() : this.props.Play();
+ this._props.playing() ? this._props.Pause() : this._props.Play();
break;
case '^':
if (!CollectionStackedTimeline.SelectingRegion) {
@@ -192,7 +195,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
CollectionStackedTimeline.SelectingRegion = this;
} else {
this._markerEnd = this.currentTime;
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
@@ -205,11 +208,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._trimming = TrimScope.None;
break;
case 'ArrowLeft':
- this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+ this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
e.stopPropagation();
break;
case 'ArrowRight':
- this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+ this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
e.stopPropagation();
break;
}
@@ -219,7 +222,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
getLinkData(l: Doc) {
let la1 = l.link_anchor_1 as Doc;
let la2 = l.link_anchor_2 as Doc;
- const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+ const linkTime = NumCast(la2[this._props.startTag], NumCast(la1[this._props.startTag]));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.link_anchor_2 as Doc;
la2 = l.link_anchor_1 as Doc;
@@ -234,9 +237,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const clientX = e.clientX;
const diff = rect ? clientX - rect?.x : null;
const shiftKey = e.shiftKey;
- if (rect && this.props.isContentActive()) {
- const wasPlaying = this.props.playing();
- if (wasPlaying) this.props.Pause();
+ if (rect && this._props.isContentActive()) {
+ const wasPlaying = this._props.playing();
+ if (wasPlaying) this._props.Pause();
var wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
@@ -258,7 +261,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = tmp;
}
if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
- const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+ const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -266,17 +269,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}),
(e, doubleTap) => {
if (e.button !== 2) {
- this.props.select(false);
- !wasPlaying && doubleTap && this.props.Play();
+ this._props.select(false);
+ !wasPlaying && doubleTap && this._props.Play();
}
},
- this.props.isSelected() || this.props.isContentActive(),
+ this._props.isSelected() || this._props.isContentActive(),
undefined,
() => {
if (shiftKey) {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this.currentTime, undefined, undefined, true);
} else {
- !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ !wasPlaying && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
}
);
@@ -291,7 +294,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
if (rect) {
this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
if (this.thumbnails) {
- const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
+ const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails);
const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined;
this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
}
@@ -306,7 +309,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this,
e,
action((e, [], []) => {
- if (rect && this.props.isContentActive()) {
+ 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);
}
return false;
@@ -326,7 +329,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this,
e,
action((e, [], []) => {
- if (rect && this.props.isContentActive()) {
+ 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);
}
return false;
@@ -349,8 +352,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
scrollToTime = (time: number) => {
if (this._timelineWrapper) {
- if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
- this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+ if (time > this.toTimeline(this._scroll + this._props.PanelWidth(), this.timelineContentWidth)) {
+ this._scroll = Math.min(this._scroll + this._props.PanelWidth(), this.timelineContentWidth - this._props.PanelWidth());
smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
} else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
@@ -364,15 +367,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
if (super.onInternalDrop(e, de)) {
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
- const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const x = localPt[0] - docDragData.offset[0];
const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
docDragData.droppedDocuments.forEach(drop => {
const anchorEnd = this.anchorEnd(drop);
if (anchorEnd !== undefined) {
- Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+ Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this._props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
}
- Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+ Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this._props.startTag : 'timecodeToShow', timelinePt, false);
});
return true;
@@ -387,22 +390,21 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// creates marker on timeline
@undoBatch
- @action
- static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
- if (anchorStartTime === undefined) return rootDoc;
+ static createAnchor(doc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
+ if (anchorStartTime === undefined) return doc;
const startTag = '_timecodeToShow';
const endTag = '_timecodeToHide';
const anchor =
docAnchor ??
Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
+ title: ComputedField.MakeFunction(`this["${endTag}"] ? "#" + formatToTime(this["${startTag}"]) + "-" + formatToTime(this["${endTag}"]) : "#" + formatToTime(this["${startTag}"])`) as any,
_label_minFontSize: 12,
_label_maxFontSize: 24,
_dragOnlyWithinContainer: true,
backgroundColor: 'rgba(128, 128, 128, 0.5)',
layout_hideLinkButton: true,
onClick: FollowLinkScript(),
- annotationOn: rootDoc,
+ annotationOn: doc,
_isTimelineLabel: true,
layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
});
@@ -423,20 +425,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
- if (this.props.playing()) this.props.Pause();
+ if (this._props.playing()) this._props.Pause();
else {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ 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();
+ if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+ this._props.Pause();
} else {
- this.props.Play();
+ this._props.Play();
}
} else {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ this._props.playFrom(seekTimeInSeconds, endTime);
this.scrollToTime(seekTimeInSeconds);
}
}
@@ -451,17 +453,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) {
- if (this.props.playing()) this.props.Pause();
- else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
+ if (this._props.playing()) this._props.Pause();
+ else if (this.layoutDoc.autoPlayAnchors) this._props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
if (this.layoutDoc.autoPlayAnchors) {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ this._props.playFrom(seekTimeInSeconds, endTime);
} else {
- this.props.setTime(seekTimeInSeconds);
+ this._props.setTime(seekTimeInSeconds);
}
}
return { select: true };
@@ -491,18 +493,18 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
};
dictationHeightPercent = 50;
- dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+ dictationHeight = () => (this._props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
@computed get timelineContentHeight() {
- return (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+ return (this._props.PanelHeight() * this.dictationHeightPercent) / 100;
}
@computed get timelineContentWidth() {
- return this.props.PanelWidth() * this.zoomFactor;
+ return this._props.PanelWidth() * this.zoomFactor;
} // subtract size of container border
- dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+ dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive();
currentTimecode = () => this.currentTime;
@@ -521,7 +523,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
@computed get timelineEvents() {
- return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+ return this._props.isContentActive() ? 'all' : this._props.isContentActive() === false ? 'none' : undefined;
}
render() {
const overlaps: {
@@ -538,8 +540,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
<div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
<div
className="collectionStackedTimeline-timelineContainer"
- style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
- onWheel={e => e.stopPropagation()}
+ style={{ width: this._props.PanelWidth(), cursor: SnappingManager.IsDragging ? 'grab' : '' }}
+ onWheel={e => this.isContentActive() && e.stopPropagation()}
onScroll={this.setScroll}
onMouseMove={e => this.isContentActive() && this.onHover(e)}
ref={wrapper => (this._timelineWrapper = wrapper)}>
@@ -554,11 +556,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
if (end < this.clipStart || start > this.clipEnd) return null;
const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
- const top = (d.level / maxLevel) * this.props.PanelHeight();
+ const top = (d.level / maxLevel) * this._props.PanelHeight();
const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
const width = (timespan / this.clipDuration) * this.timelineContentWidth;
- const height = this.props.PanelHeight() / maxLevel;
- return this.props.Document.hideAnchors ? null : (
+ const height = this._props.PanelHeight() / maxLevel;
+ return this._props.Document.hideAnchors ? null : (
<div
className={'collectionStackedTimeline-marker-timeline'}
key={d.anchor[Id]}
@@ -570,7 +572,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
pointerEvents: 'none',
}}>
<StackedTimelineAnchor
- {...this.props}
+ {...this._props}
mark={d.anchor}
rangeClickScript={this.rangeClickScript}
rangePlayScript={this.rangePlayScript}
@@ -591,18 +593,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
);
})}
{!this.IsTrimming && this.selectionContainer}
- {!this.props.PanelHeight() ? null : (
+ {!this._props.PanelHeight() ? null : (
<AudioWaveform
- rawDuration={this.props.rawDuration}
- fieldKey={this.props.dataFieldKey}
+ rawDuration={this._props.rawDuration}
+ fieldKey={this._props.dataFieldKey}
duration={this.clipDuration}
- mediaPath={this.props.mediaPath}
+ mediaPath={this._props.mediaPath}
layoutDoc={this.layoutDoc}
clipStart={this.clipStart}
clipEnd={this.clipEnd}
zoomFactor={this.zoomFactor}
PanelHeight={this.timelineContentHeight}
PanelWidth={this.timelineContentWidth}
+ progress={(this.currentTime - this.clipStart) / this.clipDuration}
/>
)}
@@ -688,41 +691,42 @@ interface StackedTimelineAnchorProps {
}
@observer
-class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
+class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnchorProps> {
_lastTimecode: number;
_disposer: IReactionDisposer | undefined;
constructor(props: any) {
super(props);
- this._lastTimecode = this.props.currentTimecode();
+ makeObservable(this);
+ this._lastTimecode = this._props.currentTimecode();
}
// updates marker document title to reflect correct timecodes
computeTitle = () => {
- if (this.props.mark.type !== DocumentType.LABEL) return undefined;
- const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
- const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ if (this._props.mark.type !== DocumentType.LABEL) return undefined;
+ const start = Math.max(NumCast(this._props.mark[this._props.startTag]), this._props.trimStart) - this._props.trimStart;
+ const end = Math.min(NumCast(this._props.mark[this._props.endTag]), this._props.trimEnd) - this._props.trimStart;
return `#${formatTime(start)}-${formatTime(end)}`;
};
componentDidMount() {
this._disposer = reaction(
- () => this.props.currentTimecode(),
+ () => 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))*/
- !this.props.layoutDoc.dontAutoFollowLinks &&
- LinkManager.Links(this.props.mark).length &&
- time > NumCast(this.props.mark[this.props.startTag]) &&
- time < NumCast(this.props.mark[this.props.endTag]) &&
- this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
+ /*(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]) &&
+ time < NumCast(this._props.mark[this._props.endTag]) &&
+ this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5
) {
- LinkFollower.FollowLink(undefined, this.props.mark, false);
+ LinkFollower.FollowLink(undefined, this._props.mark, false);
}
this._lastTimecode = time;
}
@@ -737,16 +741,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// starting the drag event for anchor resizing
@action
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
- //this.props._timeline?.setPointerCapture(e.pointerId);
+ //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);
+ return this._props.toTimeline(e.clientX - rect.x, rect.width);
};
const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
- const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== 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 && 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);
} else {
anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
@@ -760,11 +764,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
e,
e => {
if (!undo) undo = UndoManager.StartBatch('drag anchor');
- this.props.setTime(newTime(e));
+ this._props.setTime(newTime(e));
return changeAnchor(anchor, left, newTime(e));
},
action(e => {
- this.props.setTime(newTime(e));
+ this._props.setTime(newTime(e));
undo?.end();
this.noEvents = false;
}),
@@ -775,7 +779,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// context menu
contextMenuItems = () => {
const resetTitle = {
- script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!,
+ script: ScriptField.MakeFunction(
+ `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
+ )!,
icon: 'folder-plus',
label: 'Reset Title',
};
@@ -786,7 +792,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
const anchor = observable({ view: undefined as Opt<DocumentView> | null });
const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
- this.props.playLink(mark, options);
+ this._props.playLink(mark, options);
return undefined;
};
return {
@@ -794,19 +800,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
view: (
<DocumentView
key="view"
- {...this.props}
+ {...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
- DataDoc={undefined}
+ TemplateDataDocument={undefined}
docViewPath={returnEmptyDoclist}
pointerEvents={this.noEvents ? returnNone : undefined}
- styleProvider={this.props.styleProvider}
- renderDepth={this.props.renderDepth + 1}
+ styleProvider={this._props.styleProvider}
+ renderDepth={this._props.renderDepth + 1}
LayoutTemplate={undefined}
LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
- isDocumentActive={this.props.isDocumentActive}
+ isDocumentActive={this._props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
layout_fitWidth={returnTrue}
@@ -817,9 +823,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
searchFilterDocs={returnEmptyDoclist}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
- rootSelected={returnFalse}
onClick={script}
- onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+ onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
hideResizeHandles={true}
bringToFront={emptyFunction}
@@ -829,19 +834,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
};
});
- anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
- width = () => this.props.width;
- height = () => this.props.height;
+ anchorScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(-this._props.left, -this._props.top);
+ width = () => this._props.width;
+ height = () => this._props.height;
render() {
- const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
+ const inner = this.renderInner(this._props.mark, this._props.rangeClickScript, this._props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
return (
<div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
{inner.view}
{!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
<>
- <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
- <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
+ <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} />
+ <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} />
</>
)}
</div>
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index dddb3ec71..6225cc52a 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.collectionMasonryView {
display: inline;
@@ -142,7 +142,7 @@
transform-origin: top left;
grid-column-end: span 1;
height: 100%;
- margin: auto;
+ //margin: auto;
display: inline-grid;
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index da00093dd..16adf4b96 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,15 +1,15 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+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 { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
@@ -59,12 +59,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// map of node headers to their heights. Used in Masonry
@observable _heightMap = new Map<string, number>();
// Assuming that this is the current css cursor style
- @observable _cursor: CursorProperty = 'ew-resize';
+ @observable _cursor: CSS.Property.Cursor = 'ew-resize';
// gets reset whenever we scroll. Not sure what it is
@observable _scroll = 0; // used to force the document decoration to update when scrolling
// does this mean whether the browser is hidden? Or is chrome something else entirely?
@computed get chromeHidden() {
- return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
+ return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
}
// it looks like this gets the column headers that Mehek was showing just now
@computed get colHeaderData() {
@@ -77,18 +77,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// filteredChildren is what you want to work with. It's the list of things that you're currently displaying
@computed get filteredChildren() {
const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
- if (this.props.sortFunc) children.sort(this.props.sortFunc);
+ if (this._props.sortFunc) children.sort(this._props.sortFunc);
return children;
}
// how much margin we give the header
@computed get headerMargin() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin);
}
@computed get xMargin() {
- return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth()));
+ return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
}
@computed get yMargin() {
- return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth()));
+ return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
}
@computed get gridGap() {
@@ -96,7 +96,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// are we stacking or masonry?
@computed get isStackingView() {
- return (this.props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
+ return (this._props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
}
// this is the number of StackingViewFieldColumns that we have
@computed get numGroupColumns() {
@@ -108,16 +108,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// columnWidth handles the margin on the left and right side of the documents
@computed get columnWidth() {
- return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
+ return Math.min(this._props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this._props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
}
@computed get NodeWidth() {
- return this.props.PanelWidth() - this.gridGap;
+ return this._props.PanelWidth() - this.gridGap;
}
constructor(props: any) {
super(props);
-
+ makeObservable(this);
if (this.colHeaderData === undefined) {
// TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
// here we're making an empty list of column headers (again, what Mehek showed us)
@@ -138,7 +138,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
// just getting the style
- const style = this.isStackingView ? { margin: this.rootDoc._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
@@ -203,7 +203,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
componentDidMount() {
super.componentDidMount?.();
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
@@ -214,7 +214,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
() => this.layoutDoc._layout_autoHeight,
layout_autoHeight =>
layout_autoHeight &&
- this.props.setHeight?.(
+ this._props.setHeight?.(
Math.min(
NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0))
@@ -229,20 +229,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._layout_autoHeightDisposer?.();
}
- isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+ isAnyChildContentActive = () => this._props.isAnyChildContentActive();
- @action
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
+ return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
};
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
};
- onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
- return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ return () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
}
scrollToBottom = () => {
@@ -256,7 +255,7 @@ 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 localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+ const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
let focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -268,100 +267,100 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
if (property === StyleProp.Opacity && doc) {
- if (this.props.childOpacity) {
- return this.props.childOpacity();
+ if (this._props.childOpacity) {
+ return this._props.childOpacity();
}
if (this.Document._currentFrame !== undefined) {
return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
}
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
e.stopPropagation?.();
const below = !e.altKey && e.key !== 'Tab';
const layout_fieldKey = StrCast(docView.LayoutFieldKey);
- const newDoc = Doc.MakeCopy(docView.rootDoc, true);
- const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ const newDoc = Doc.MakeCopy(docView.Document, true);
+ const dataField = docView.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' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
+ if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) {
+ newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
}
Doc.GetProto(newDoc).text = undefined;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
};
- isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+ isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
@observable _renderCount = 5;
isChildContentActive = () =>
- this.props.isContentActive?.() === false
- ? false
- : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive))
- ? true
- : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false
+ this._props.isContentActive?.() === false
? false
- : undefined;
- isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
+ : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+ ? true
+ : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+ ? false
+ : undefined;
+ isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
@observable docRefs = new ObservableMap<Doc, DocumentView>();
+ childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
getDisplayDoc(doc: Doc, width: () => number, count: number) {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
+ const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const height = () => this.getDocHeight(doc);
-
+ const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
+ const panelWidth = () => (this.isStackingView ? width() : this.columnWidth);
const stackedDocTransform = () => this.getDocTransform(doc);
this._docXfs.push({ stackedDocTransform, width, height });
return count > this._renderCount ? null : (
<DocumentView
ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
Document={doc}
- DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
- renderDepth={this.props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+ TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])}
+ renderDepth={this._props.renderDepth + 1}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
+ pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
styleProvider={this.styleProvider}
- docViewPath={this.props.docViewPath}
- layout_fitWidth={this.props.childLayoutFitWidth}
+ docViewPath={this._props.docViewPath}
+ layout_fitWidth={this.childFitWidth}
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
- onBrowseClick={this.props.onBrowseClick}
+ onBrowseClick={this._props.onBrowseClick}
isDocumentActive={this.isContentActive}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
- dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
- dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
+ dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)}
+ dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
rootSelected={this.rootSelected}
- layout_showTitle={this.props.childlayout_showTitle}
- dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+ layout_showTitle={this._props.childlayout_showTitle}
+ dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={stackedDocTransform}
focus={this.focusDocument}
childFilters={this.childDocFilters}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideTitle={this.props.childHideTitle?.()}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ hideResizeHandles={this._props.childHideResizeHandles}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
- yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
+ xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
+ yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
bringToFront={returnFalse}
- pinToPres={this.props.pinToPres}
+ pinToPres={this._props.pinToPres}
/>
);
}
@@ -371,31 +370,31 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
- return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+ return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale);
}
getDocWidth(d?: Doc) {
if (!d) return 0;
- const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
const maxWidth = this.columnWidth / this.numGroupColumns;
- if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
+ if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
return Math.min(NumCast(d._width), maxWidth);
}
return maxWidth;
}
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
- const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
- const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
- const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
- const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
+ const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+ const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
+ const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0);
+ const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
if (nw && nh) {
const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this._props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
@@ -433,7 +432,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
// Fairly confident that this is where the swapping of nodes in the various arrays happens
const where = [de.x, de.y];
@@ -471,9 +469,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return true;
}
- } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
- if (!this.props.addDocument?.(source)) e.preventDefault();
+ if (!this._props.addDocument?.(source)) e.preventDefault();
de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
e.stopPropagation();
return true;
@@ -489,14 +487,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const dropCreator = annoDragData.dropDocCreator;
annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
const dropDoc = dropCreator(annotationOn);
- return dropDoc || this.rootDoc;
+ return dropDoc || this.Document;
};
return true;
}
/// an item from outside of Dash is being dropped onto this stacking view (e.g, a document from the file system)
@undoBatch
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const where = [e.clientX, e.clientY];
let targInd = -1;
@@ -541,10 +538,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
- if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- this.props.setHeight?.(height);
+ if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+ this._props.setHeight?.(height);
}
}
})
@@ -555,8 +552,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
- Document={this.props.Document}
- DataDoc={this.props.DataDoc}
+ Document={this._props.Document}
+ TemplateDataDocument={this._props.TemplateDataDocument}
renderChildren={this.children}
columnWidth={this.columnWidth}
numGroupColumns={this.numGroupColumns}
@@ -570,7 +567,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
yMargin={this.yMargin}
type={type}
createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
+ screenToLocalTransform={this._props.ScreenToLocalTransform}
/>
);
};
@@ -583,11 +580,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
- const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
+ const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
return (
<CollectionMasonryViewFieldRow
showHandle={first}
- Document={this.props.Document}
+ Document={this._props.Document}
chromeHidden={this.chromeHidden}
pivotField={this.pivotField}
unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
@@ -596,9 +593,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.refList.push(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
- this.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
+ this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
}
})
);
@@ -614,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
parent={this}
type={type}
createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
+ screenToLocalTransform={this._props.ScreenToLocalTransform}
setDocHeight={this.setDocHeight}
/>
);
@@ -664,56 +661,50 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return35 = () => 35;
@computed get buttonMenu() {
- const menuDoc: Doc = Cast(this.rootDoc.layout_headerButton, Doc, null);
- // TODO:glr Allow support for multiple buttons
- if (menuDoc) {
- const width: number = NumCast(menuDoc._width, 30);
- const height: number = NumCast(menuDoc._height, 30);
- return (
- <div className="buttonMenu-docBtn" style={{ width: width, height: height }}>
- <DocumentView
- Document={menuDoc}
- DataDoc={menuDoc}
- isContentActive={this.isContentActive}
- isDocumentActive={this.isContentActive}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- onBrowseClick={this.props.onBrowseClick}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.return35}
- PanelHeight={this.return35}
- renderDepth={this.props.renderDepth}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
- />
- </div>
- );
- }
+ const menuDoc = DocCast(this.layoutDoc.layout_headerButton);
+ return !menuDoc ? null : (
+ <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+ <DocumentView
+ Document={menuDoc}
+ isContentActive={this.isContentActive}
+ isDocumentActive={this.isContentActive}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ addDocTab={this._props.addDocTab}
+ onBrowseClick={this._props.onBrowseClick}
+ pinToPres={emptyFunction}
+ rootSelected={this.rootSelected}
+ removeDocument={this._props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.return35}
+ PanelHeight={this.return35}
+ renderDepth={this._props.renderDepth}
+ focus={emptyFunction}
+ styleProvider={this._props.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
+ />
+ </div>
+ );
}
@computed get nativeWidth() {
- return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
+ return this._props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
}
@computed get nativeHeight() {
- return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
+ return this._props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
}
@computed get scaling() {
- return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight;
+ return !this.nativeWidth ? 1 : this._props.PanelHeight() / this.nativeHeight;
}
@computed get backgroundEvents() {
- return this.props.isContentActive() === false ? 'none' : undefined;
+ return this._props.isContentActive() === false ? 'none' : undefined;
}
observer: any;
render() {
@@ -723,8 +714,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
SetValue: this.addGroup,
contents: '+ ADD A GROUP',
};
- const buttonMenu = this.rootDoc.layout_headerButton;
- const noviceExplainer = this.rootDoc.layout_explainer;
+ const buttonMenu = this.layoutDoc.layout_headerButton;
+ const noviceExplainer = this.layoutDoc.layout_explainer;
return (
<>
{buttonMenu || noviceExplainer ? (
@@ -739,8 +730,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
ref={this.createRef}
style={{
overflowY: this.isContentActive() ? 'auto' : 'hidden',
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents,
+ 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))}
onDrop={this.onExternalDrop.bind(this)}
@@ -748,7 +739,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onWheel={e => this.isContentActive() && e.stopPropagation()}>
{this.renderedSections}
{!this.showAddAGroup ? null : (
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+ <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>
)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3598d548a..f9d575da2 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,15 +1,15 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from '../../../Utils';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
@@ -19,14 +19,14 @@ import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
-import './CollectionStackingView.scss';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { Id } from '../../../fields/FieldSymbols';
+import './CollectionStackingView.scss';
+import { ObservableReactComponent } from '../ObservableReactComponent';
// So this is how we are storing a column
interface CSVFieldColumnProps {
Document: Doc;
- DataDoc: Opt<Doc>;
+ TemplateDataDocument: Opt<Doc>;
docList: Doc[];
heading: string;
pivotField: string;
@@ -49,16 +49,21 @@ interface CSVFieldColumnProps {
}
@observer
-export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
- @observable private _background = 'inherit';
-
+export class CollectionStackingViewFieldColumn extends ObservableReactComponent<CSVFieldColumnProps> {
private dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
-
+ @observable private _background = 'inherit';
@observable _paletteOn = false;
- @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
- @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
+ @observable _heading = '';
+ @observable _color = '';
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
+ this._color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb';
+ }
_ele: HTMLElement | null = null;
@@ -68,7 +73,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this.dropDisposer?.();
if (ele) {
this._ele = ele;
- this.props.observeHeight(ele);
+ this._props.observeHeight(ele);
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
};
@@ -76,21 +81,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
componentDidMount() {
this._disposers.collapser = reaction(
- () => this.props.headingObject?.collapsed,
+ () => this._props.headingObject?.collapsed,
collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
{ fireImmediately: true }
);
}
componentWillUnmount() {
this._disposers.collapser?.();
- this.props.unobserveHeight(this._ele);
+ this._props.unobserveHeight(this._ele);
}
//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) };
- this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+ this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false));
return true;
});
getValue = (value: string): any => {
@@ -105,13 +110,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
headingChanged = (value: string, shiftDown?: boolean) => {
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
+ if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- 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;
+ 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;
}
return true;
}
@@ -120,41 +125,41 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
changeColumnColor = (color: string) => {
- this.props.headingObject?.setColor(color);
+ this._props.headingObject?.setColor(color);
this._color = color;
};
- @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
+ @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
@action pointerLeave = () => (this._background = 'inherit');
@undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
- newDoc[key] = this.getValue(this.props.heading);
- const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
- const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
+ 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 heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
- return this.props.addDocument?.(newDoc) || false;
+ return this._props.addDocument?.(newDoc) || false;
};
@action
deleteColumn = () => {
- 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);
+ 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);
}
};
@action
collapseSection = () => {
- this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed);
- this.collapsed = BoolCast(this.props.headingObject?.collapsed);
+ this._props.headingObject?.setCollapsed(!this._props.headingObject.collapsed);
+ this.collapsed = BoolCast(this._props.headingObject?.collapsed);
};
headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
@@ -162,12 +167,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
//TODO: I think this is where I'm supposed to edit stuff
startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
// 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);
+ const embedding = Doc.MakeEmbedding(this._props.Document);
+ embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
embedding._pivotField = undefined;
let value = this.getValue(this._heading);
value = typeof value === 'string' ? `"${value}"` : value;
- embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
+ embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name });
if (embedding.viewSpecScript) {
DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
return true;
@@ -177,7 +182,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
renderColorPicker = () => {
const gray = '#f1efeb';
- const selected = this.props.headingObject ? this.props.headingObject.color : gray;
+ const selected = this._props.headingObject ? this._props.headingObject.color : gray;
const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1'];
return (
<div className="collectionStackingView-colorPicker">
@@ -211,15 +216,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
- const dataDoc = this.props.DataDoc || this.props.Document;
+ const dataDoc = this._props.TemplateDataDocument || this._props.Document;
const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
- FormattedTextBox.SelectOnLoad = doc[Id];
- return this.props.addDocument?.(doc);
+ FormattedTextBox.SetSelectOnLoad(doc);
+ return this._props.addDocument?.(doc);
},
- this.props.addDocument,
+ this._props.addDocument,
0,
0,
true
@@ -231,12 +236,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
docItems.push({
description: ':' + fieldKey,
event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ if (this._props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
- return this.props.addDocument?.(created);
+ return this._props.addDocument?.(created);
}
},
icon: 'compress-arrows-alt',
@@ -250,12 +255,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
event: () => {
const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
if (created) {
- const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
if (container.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, container);
return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
}
- return this.props.addDocument?.(created) || false;
+ return this._props.addDocument?.(created) || false;
}
},
icon: 'compress-arrows-alt',
@@ -264,16 +269,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
- Doc.GetProto(this.props.Document)[name] = '';
+ Doc.GetProto(this._props.Document)[name] = '';
const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ if (this._props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
- this.props.addDocument?.(created);
+ this._props.addDocument?.(created);
}
});
- const pt = this.props
+ const pt = this._props
.screenToLocalTransform()
.inverse()
.transformPoint(width - 30, height);
@@ -282,21 +287,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@computed get innards() {
TraceMobx();
- const key = this.props.pivotField;
- const headings = this.props.headings();
+ const key = this._props.pivotField;
+ const headings = this._props.headings();
const heading = this._heading;
- const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
+ 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 headingView = this.props.headingObject ? (
+ const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+ const headingView = this._props.headingObject ? (
<div
key={heading}
className="collectionStackingView-sectionHeader"
ref={this._headerRef}
style={{
- marginTop: this.props.yMargin,
- width: this.props.columnWidth / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1),
+ marginTop: this._props.yMargin,
+ width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
}}>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
@@ -326,35 +331,35 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
)} */}
</div>
<div
- className={'collectionStackingView-collapseBar' + (this.props.headingObject.collapsed === true ? ' active' : '')}
- style={{ display: this.props.headingObject.collapsed === true ? 'block' : undefined }}
+ className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')}
+ style={{ display: this._props.headingObject.collapsed === true ? 'block' : undefined }}
onClick={this.collapseSection}
/>
</div>
) : null;
- const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
- const type = this.props.Document.type;
+ const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
+ const type = this._props.Document.type;
return (
<>
- {this.props.Document._columnsHideIfEmpty ? null : headingView}
+ {this._props.Document._columnsHideIfEmpty ? null : headingView}
{this.collapsed ? null : (
<div>
<div
key={`${heading}-stack`}
className={`collectionStackingView-masonrySingle`}
style={{
- padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: 'auto',
width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
- gridGap: this.props.gridGap,
+ gridGap: this._props.gridGap,
gridTemplateColumns: templatecols,
gridAutoRows: '0px',
}}>
- {this.props.renderChildren(this.props.docList)}
+ {this._props.renderChildren(this._props.docList)}
</div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+ {!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
@@ -365,7 +370,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
key={`${heading}-add-document`}
onKeyDown={e => e.stopPropagation()}
className="collectionStackingView-addDocumentButton"
- style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
+ style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}>
<EditableView
GetValue={returnEmptyString}
SetValue={this.addNewTextDoc}
@@ -384,15 +389,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
render() {
TraceMobx();
- const headings = this.props.headings();
+ const headings = this._props.headings();
const heading = this._heading;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
<div
- className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+ className={'collectionStackingViewFieldColumn' + (SnappingManager.IsDragging ? 'Dragging' : '')}
key={heading}
style={{
- width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`,
+ width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`,
height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
background: this._background,
}}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 328b060c4..b56973dc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,41 +1,46 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
+import * as React from 'react';
import * as rp from 'request-promise';
+import { Utils, returnFalse } from '../../../Utils';
import CursorField from '../../../fields/CursorField';
import { Doc, DocListCast, Field, 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 { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { returnFalse, Utils } from '../../../Utils';
import { DocServer } from '../../DocServer';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { InteractionUtils } from '../../util/InteractionUtils';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { DocComponent } from '../DocComponent';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { LoadingBox } from '../nodes/LoadingBox';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { CollectionView, CollectionViewProps } from './CollectionView';
-import React = require('react');
-import { LoadingBox } from '../nodes/LoadingBox';
export interface SubCollectionViewProps extends CollectionViewProps {
isAnyChildContentActive: () => boolean;
}
export function CollectionSubView<X>(moreProps?: X) {
- class CollectionSubView extends DocComponent<X & SubCollectionViewProps>() {
+ class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected _mainCont?: HTMLDivElement;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
@observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
@@ -56,24 +61,29 @@ export function CollectionSubView<X>(moreProps?: X) {
this.gestureDisposer?.();
}
- @computed get dataDoc() {
- return this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+ get dataDoc() {
+ return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField
+ ? Doc.GetProto(this._props.TemplateDataDocument)
+ : this._props.Document.resolvedDataDoc
+ ? this._props.Document
+ : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
}
- rootSelected = () => this.props.isSelected() || (this.rootDoc && this.props.rootSelected());
+ // this returns whether either the collection is selected, or the template that it is part of is selected
+ rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
- // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
- // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
+ // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
+ // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
- return this.layoutDoc[this.props.fieldKey];
+ return this.dataDoc[this._props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code.
}
@computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
- const { Document, DataDoc } = this.props;
+ const { Document, TemplateDataDocument } = this._props;
const validPairs = this.childDocs
- .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc))
+ .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));
@@ -83,14 +93,14 @@ export function CollectionSubView<X>(moreProps?: X) {
@computed get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
- collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters);
- collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._childFiltersByRanges, listSpec('string'), []);
+ collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters);
+ collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.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 => Utils.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)) || [])];
- childDocRangeFilters = () => [...(this.props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
- searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
+ unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+ childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
+ searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs);
@computed.struct get childDocs() {
TraceMobx();
let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -100,31 +110,31 @@ export function CollectionSubView<X>(moreProps?: X) {
} else if (Cast(this.dataField, listSpec(Doc), null)) {
// otherwise, if the collection data is a list, then use it.
rawdocs = Cast(this.dataField, listSpec(Doc), null);
- } else {
+ } else if (this.dataField) {
// Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
// For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
// Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
- const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
- rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : [];
+ const templateRoot = this._props.TemplateDataDocument;
+ rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
}
- const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
+ const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
const childDocFilters = this.childDocFilters();
const childFiltersByRanges = this.childDocRangeFilters();
const searchDocs = this.searchFilterDocs();
- if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
+ if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
}
const docsforFilter: Doc[] = [];
childDocs.forEach(d => {
// dragging facets
- const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
- if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false;
- let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
+ const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
+ if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
+ let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0;
if (notFiltered) {
- notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0;
+ notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.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];
@@ -157,7 +167,7 @@ export function CollectionSubView<X>(moreProps?: X) {
@action
protected async setCursorPosition(position: [number, number]) {
let ind;
- const doc = this.props.Document;
+ const doc = this._props.Document;
const id = Doc.UserDoc()[Id];
const email = Doc.CurrentUserEmail;
const pos = { x: position[0], y: position[1] };
@@ -193,23 +203,23 @@ export function CollectionSubView<X>(moreProps?: X) {
const dropAction = this.layoutDoc.dropAction as dropActionType;
// if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
// so we check if our collection has a dropAction set on it and if so, we use that instead.
- if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
+ if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) {
de.complete.docDragData.dropAction = dropAction;
}
e.stopPropagation();
}
}
- 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);
- @action
+ 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;
+
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
if (docDragData) {
let added = undefined;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
- const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
+ const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
@@ -217,28 +227,28 @@ export function CollectionSubView<X>(moreProps?: X) {
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
const canAdd =
- (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc));
- const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
+ (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
+ const moved = docDragData.moveDocument(movedDocs, this.Document, canAdd ? this.addDocument : returnFalse);
added = canAdd || moved ? moved : undefined;
} else if (addedDocs.length) {
added = this.addDocument(addedDocs);
}
- if (!added && ScriptCast(this.rootDoc.dropConverter)) {
- ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
+ if (!added && ScriptCast(this.Document.dropConverter)) {
+ ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData });
added = addedDocs.length ? this.addDocument(addedDocs) : true;
}
} else {
- ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
+ ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData });
added = this.addDocument(docDragData.droppedDocuments);
!added && alert('You cannot perform this move');
}
- added === false && !this.props.isAnnotationOverlay && e.preventDefault();
+ added === false && !this._props.isAnnotationOverlay && e.preventDefault();
added === true && e.stopPropagation();
return added ? true : false;
} else if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
- const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined);
+ const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
this.addDocument(dropped);
return dropped;
};
@@ -248,7 +258,6 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@undoBatch
- @action
protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) {
if (e.ctrlKey) {
e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
@@ -330,7 +339,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
});
} else {
- const srcWeb = SelectionManager.Views().lastElement();
+ 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;
@@ -339,7 +348,7 @@ export function CollectionSubView<X>(moreProps?: X) {
Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
addDocument(htmlDoc);
if (srcWeb) {
- const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+ 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);
@@ -459,15 +468,15 @@ export function CollectionSubView<X>(moreProps?: X) {
}
if (generatedDocuments.length) {
// Creating a dash document
- const isFreeformView = this.props.Document._type_collection === CollectionViewType.Freeform;
+ const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform;
const set = !isFreeformView
? generatedDocuments
: generatedDocuments.length > 1
- ? generatedDocuments.map(d => {
- DocUtils.iconify(d);
- return d;
- })
- : [];
+ ? generatedDocuments.map(d => {
+ DocUtils.iconify(d);
+ return d;
+ })
+ : [];
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index a8f5345b7..6033b897d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,5 +1,5 @@
import { toUpper } from 'lodash';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
@@ -21,7 +21,7 @@ import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './coll
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
-import React = require('react');
+import * as React from 'react';
@observer
export class CollectionTimeView extends CollectionSubView() {
@@ -32,10 +32,15 @@ export class CollectionTimeView extends CollectionSubView() {
@observable _viewDefDivClick: Opt<ScriptField>;
@observable _focusPivotField: Opt<string>;
- async componentDidMount() {
- this.props.setContentView?.(this);
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ componentDidMount() {
+ this._props.setContentView?.(this);
runInAction(() => {
- this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
+ this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name });
this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
});
}
@@ -47,16 +52,16 @@ export class CollectionTimeView extends CollectionSubView() {
getAnchor = (addAsAnnotation: boolean) => {
const anchor = Docs.Create.HTMLMarkerDocument([], {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
- annotationOn: this.rootDoc,
+ annotationOn: this.Document,
});
- PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document);
if (addAsAnnotation) {
// when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
- if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
@@ -79,10 +84,10 @@ export class CollectionTimeView extends CollectionSubView() {
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
- const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
- this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
- this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined;
+ 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();
+ this.Document[this._props.fieldKey + '-timelineSpan'] = undefined;
return false;
}),
returnFalse,
@@ -95,9 +100,9 @@ export class CollectionTimeView extends CollectionSubView() {
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
- const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
- this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+ 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();
return false;
}),
returnFalse,
@@ -110,10 +115,10 @@ export class CollectionTimeView extends CollectionSubView() {
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
- const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
- this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
- this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+ 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();
+ this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
return false;
}),
returnFalse,
@@ -140,9 +145,9 @@ export class CollectionTimeView extends CollectionSubView() {
@computed get contents() {
return (
- <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
+ <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
fitContentsToBox={returnTrue}
childClickScript={this._childClickedScript}
@@ -189,7 +194,7 @@ export class CollectionTimeView extends CollectionSubView() {
@computed get _allFacets() {
const facets = new Set<string>();
this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
- Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+ Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
return Array.from(facets);
}
menuCallback = (x: number, y: number) => {
@@ -206,7 +211,7 @@ export class CollectionTimeView extends CollectionSubView() {
Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, ':');
};
@@ -222,7 +227,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
return false;
}}
- background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb";
contents={':' + StrCast(this.layoutDoc._pivotField)}
showMenuOnLoad={true}
display={'inline'}
@@ -241,7 +246,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
});
const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
- const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
+ const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this._props.PanelWidth() / this._props.PanelHeight() > 6;
if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) {
if (!this._changing) {
this._changing = true;
@@ -256,10 +261,10 @@ export class CollectionTimeView extends CollectionSubView() {
}
return (
- <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this.props.PanelWidth(), height: '100%' }}>
+ <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}>
{this.pivotKeyUI}
{this.contents}
- {!this.props.isSelected() || !doTimeline ? null : (
+ {!this._props.isSelected() || !doTimeline ? null : (
<>
<div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
<div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 21efeba44..bbbef78b4 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.collectionTreeView-container {
transform-origin: top left;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 761192a22..7f8d42088 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,5 +1,6 @@
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -26,7 +27,6 @@ import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
-import React = require('react');
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -59,27 +59,29 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes
private observer: any; // observer for monitoring tree view items.
- @computed get doc() {
- return this.props.Document;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
}
- @computed get dataDoc() {
- return this.props.DataDoc || this.doc;
+
+ get dataDoc() {
+ return this._props.TemplateDataDocument || this.Document;
}
@computed get treeViewtruncateTitleWidth() {
- return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth());
+ return NumCast(this.Document.treeView_TruncateTitleWidth, this.panelWidth());
}
@computed get treeChildren() {
TraceMobx();
- return this.props.childDocuments || this.childDocs;
+ return this._props.childDocuments || this.childDocs;
}
@computed get outlineMode() {
- return this.doc.treeView_Type === TreeViewType.outline;
+ return this.Document.treeView_Type === TreeViewType.outline;
}
@computed get fileSysMode() {
- return this.doc.treeView_Type === TreeViewType.fileSystem;
+ return this.Document.treeView_Type === TreeViewType.fileSystem;
}
@computed get dashboardMode() {
- return this.doc === Doc.MyDashboards;
+ return this.Document === Doc.MyDashboards;
}
@observable _titleHeight = 0; // height of the title bar
@@ -88,8 +90,8 @@ 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 = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
componentWillUnmount() {
this._isDisposing = true;
@@ -99,9 +101,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
componentDidMount() {
- //this.props.setContentView?.(this);
+ //this._props.setContentView?.(this);
this._disposers.autoheight = reaction(
- () => this.rootDoc.layout_autoHeight,
+ () => this.layoutDoc.layout_autoHeight,
auto => auto && this.computeHeight(),
{ fireImmediately: true }
);
@@ -112,38 +114,38 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
this.layoutDoc._layout_autoHeightMargins = bodyHeight;
- !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight);
+ !this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight);
}
};
unobserveHeight = (ref: any) => {
this.refList.delete(ref);
- this.rootDoc.layout_autoHeight && this.computeHeight();
+ this.layoutDoc.layout_autoHeight && this.computeHeight();
};
observeHeight = (ref: any) => {
if (ref) {
this.refList.add(ref);
this.observer = new _global.ResizeObserver(
action((entries: any) => {
- if (this.rootDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
+ if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
this.computeHeight();
}
})
);
- this.rootDoc.layout_autoHeight && 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.doc, this.onInternalPreDrop.bind(this));
+ if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
};
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
const dropAction = this.layoutDoc.dropAction as dropActionType;
const dragData = de.complete.docDragData;
if (dragData) {
- const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false;
- const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
+ const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
+ const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
if (isAlreadyInTree() !== sameTree) {
console.log('WHAAAT');
}
@@ -152,22 +154,22 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
};
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+ screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight);
@action
remove = (doc: Doc | Doc[]): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- const targetDataDoc = this.doc[DocData];
- const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+ 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.rootDoc, doc)))) SelectionManager.DeselectAll();
+ if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
if (result.length !== value.length && doc instanceof Doc) {
- const ind = DocListCast(targetDataDoc[this.props.fieldKey]).indexOf(doc);
- const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1];
- this.props.removeDocument?.(doc);
+ const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+ const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
+ this._props.removeDocument?.(doc);
if (ind > 0 && prev) {
- FormattedTextBox.SelectOnLoad = prev[Id];
- DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
+ FormattedTextBox.SetSelectOnLoad(prev);
+ DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false);
}
return true;
}
@@ -178,24 +180,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
const doAddDoc = (doc: Doc | Doc[]) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
- const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
- res && Doc.SetContainer(doc, this.props.Document);
+ const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before);
+ res && Doc.SetContainer(doc, this.Document);
return res;
}, true);
- if (this.doc.resolvedDataDoc instanceof Promise) return false;
- return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs);
+ if (this.Document.resolvedDataDoc instanceof Promise) return false;
+ return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs);
};
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!Doc.noviceMode) {
const layoutItems: ContextMenuProps[] = [];
- layoutItems.push({ description: 'Make tree state ' + (this.doc.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeView_OpenIsTransient = !this.doc.treeView_OpenIsTransient), icon: 'paint-brush' });
- layoutItems.push({ description: (this.doc.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeView_HideHeaderFields = !this.doc.treeView_HideHeaderFields), icon: 'paint-brush' });
- layoutItems.push({ description: (this.doc.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeView_HideTitle = !this.doc.treeView_HideTitle), icon: 'paint-brush' });
+ layoutItems.push({
+ description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
+ event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+ 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' });
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
+ onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
!existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
};
@@ -213,7 +219,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
height={'auto'}
GetValue={() => StrCast(this.dataDoc.title)}
SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
- if (enter && this.props.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
+ if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
this.dataDoc.title = value;
return true;
})}
@@ -231,12 +237,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
get documentTitle() {
return (
<FormattedTextBox
- {...this.props}
+ {...this._props}
fieldKey="text"
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
- rootSelected={returnTrue}
forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
PanelWidth={this.documentTitleWidth}
PanelHeight={this.documentTitleHeight}
@@ -254,52 +259,52 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
childContextMenuItems = () => {
- const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- const icons = StrListCast(this.doc.childContextMenuIcons);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.Document.childContextMenuIcons);
+ return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeView_HideHeaderFields);
+ headerFields = () => this._props.treeViewHideHeaderFields || BoolCast(this.Document.treeView_HideHeaderFields);
@observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
- const dragAction = StrCast(this.doc.childDragAction) as dropActionType;
+ const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
- const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
+ 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))));
return TreeView.GetChildElements(
this.treeChildren,
this,
this,
- this.doc,
- this.props.DataDoc,
+ this.Document,
+ this._props.TemplateDataDocument,
undefined,
undefined,
addDoc,
this.remove,
moveDoc,
dragAction,
- this.props.addDocTab,
- this.props.styleProvider,
+ this._props.addDocTab,
+ this._props.styleProvider,
this.screenToLocalTransform,
this.isContentActive,
this.panelWidth,
- this.props.renderDepth,
+ this._props.renderDepth,
this.headerFields,
[],
- this.props.onCheckedClick,
+ this._props.onCheckedClick,
this.onChildClick,
- this.props.treeViewSkipFields,
+ this._props.treeViewSkipFields,
true,
this.whenChildContentsActiveChanged,
- this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null),
+ this._props.dontRegisterView || Cast(this.Document.childDontRegisterViews, 'boolean', null),
this.observeHeight,
this.unobserveHeight,
this.childContextMenuItems(),
//TODO: [AL] add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex,
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex,
this._renderCount
);
}
@@ -307,8 +312,8 @@ 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.props.ScreenToLocalTransform().Scale))}
- key={this.doc[Id]}
+ ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))}
+ key={this.Document[Id]}
style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
</div>
@@ -316,38 +321,38 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
@computed get noviceExplainer() {
- return !Doc.noviceMode || !this.rootDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.layout_explainer)} </div>;
+ return !Doc.noviceMode || !this.layoutDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.layoutDoc.layout_explainer)} </div>;
}
return35 = () => 35;
@computed get buttonMenu() {
- const menuDoc = Cast(this.rootDoc.layout_headerButton, Doc, null);
+ const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null);
// To create a multibutton menu add a CollectionLinearView
return !menuDoc ? null : (
<div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
<DocumentView
Document={menuDoc}
- DataDoc={menuDoc}
- isContentActive={this.props.isContentActive}
+ TemplateDataDocument={menuDoc}
+ isContentActive={this._props.isContentActive}
isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
+ rootSelected={this.rootSelected}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.return35}
PanelHeight={this.return35}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
focus={emptyFunction}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
/>
</div>
);
@@ -364,30 +369,30 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@computed get nativeDimScaling() {
const nw = this.nativeWidth;
const nh = this.nativeHeight;
- const hscale = nh ? this.props.PanelHeight() / nh : 1;
- const wscale = nw ? this.props.PanelWidth() / nw : 1;
+ const hscale = nh ? this._props.PanelHeight() / nh : 1;
+ const wscale = nw ? this._props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
- marginX = () => NumCast(this.doc._xMargin);
- marginTop = () => NumCast(this.doc._yMargin);
- marginBot = () => NumCast(this.doc._yMargin);
+ marginX = () => NumCast(this.Document._xMargin);
+ marginTop = () => NumCast(this.Document._yMargin);
+ marginBot = () => NumCast(this.Document._yMargin);
documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
- onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
- panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
+ onChildClick = () => this._props.onChildClick?.() || ScriptCast(this.Document.onChildClick);
+ panelWidth = () => Math.max(0, this._props.PanelWidth() - 2 * this.marginX() * (this._props.NativeDimScaling?.() || 1));
- addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}_annotations`) || false;
- remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}_annotations`) || false;
+ 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;
+ this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
@observable _headerHeight = 0;
@computed get content() {
- const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
- const color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color);
- const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined);
- const titleBar = this.props.treeViewHideTitle || this.doc.treeView_HideTitle ? null : this.titleBar;
+ const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
+ const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color);
+ const pointerEvents = () => (this._props.isContentActive() === false ? 'none' : undefined);
+ const titleBar = this._props.treeViewHideTitle || this.Document.treeView_HideTitle ? null : this.titleBar;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
@@ -399,7 +404,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div
className="collectionTreeView-contents"
key="tree"
- ref={r => !this.doc.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
+ ref={r => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
style={{
...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
color: color(),
@@ -424,7 +429,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={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
onDrop={this.onTreeDrop}>
<ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
</div>
@@ -436,27 +441,27 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
render() {
TraceMobx();
- const scale = this.props.NativeDimScaling?.() || 1;
+ const scale = this._props.NativeDimScaling?.() || 1;
return (
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
- {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
+ {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
+ pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
isAnnotationOverlay={true}
isAnnotationOverlayScrollable={true}
- childDocumentsActive={this.props.isDocumentActive}
- fieldKey={this.props.fieldKey + '_annotations'}
+ childDocumentsActive={this._props.isDocumentActive}
+ fieldKey={this._props.fieldKey + '_annotations'}
dropAction="move"
select={emptyFunction}
addDocument={this.addAnnotationDocument}
removeDocument={this.remAnnotationDocument}
moveDocument={this.moveAnnotationDocument}
bringToFront={emptyFunction}
- renderDepth={this.props.renderDepth + 1}>
+ renderDepth={this._props.renderDepth + 1}>
{this.content}
</CollectionFreeFormView>
) : (
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 5db489c0a..de53a2c62 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
.collectionView {
border-width: 0;
@@ -9,7 +9,7 @@
border-radius: inherit;
width: 100%;
height: 100%;
- overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
+ //overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
.collectionView-filterDragger {
background-color: rgb(140, 139, 139);
@@ -54,8 +54,8 @@
}
}
- >div,
- >div>div {
+ > div,
+ > div > div {
width: 100%;
height: 100%;
}
@@ -80,4 +80,4 @@
border-radius: 50%;
padding: 3px;
background: white;
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 389a9a534..0237ec95e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,17 +1,16 @@
-import { IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+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 { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
import { dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { InteractionUtils } from '../../util/InteractionUtils';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -20,19 +19,20 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionLinearView } from './collectionLinear';
-import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionNoteTakingView } from './CollectionNoteTakingView';
import { CollectionPileView } from './CollectionPileView';
-import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
import { CollectionStackingView } from './CollectionStackingView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
+import { CollectionCalendarView} from './CollectionCalendarView';
+import { CollectionLinearView } from './collectionLinear';
+import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
+import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
const path = require('path');
interface CollectionViewProps_ extends FieldViewProps {
@@ -50,10 +50,9 @@ interface CollectionViewProps_ extends FieldViewProps {
childlayout_showTitle?: () => string;
childOpacity?: () => number;
childContextMenuItems?: () => { script: ScriptField; label: string }[];
- childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
- childHideDecorationTitle?: () => boolean;
- childHideResizeHandles?: () => boolean;
childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+ childHideDecorationTitle?: boolean;
+ childHideResizeHandles?: boolean;
childDragAction?: dropActionType;
childXPadding?: number;
childYPadding?: number;
@@ -78,11 +77,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
this._safeMode = safeMode;
}
private reactionDisposer: IReactionDisposer | undefined;
- @observable _isContentActive: boolean | undefined;
+ @observable _isContentActive: boolean | undefined = undefined;
constructor(props: any) {
super(props);
- runInAction(() => (this._annotationKeySuffix = returnEmptyString));
+ makeObservable(this);
+ this._annotationKeySuffix = returnEmptyString;
}
componentDidMount() {
@@ -91,7 +91,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
// will cause downstream invalidations even if the computed value doesn't change. By making
// this a reaction, downstream invalidations only occur when the reaction value actually changes.
this.reactionDisposer = reaction(
- () => (this.isAnyChildContentActive() ? true : this.props.isContentActive()),
+ () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
active => (this._isContentActive = active),
{ fireImmediately: true }
);
@@ -114,7 +114,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
return viewField as any as CollectionViewType;
}
- screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()));
+ screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
// prettier-ignore
private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
TraceMobx();
@@ -122,8 +122,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
switch (type) {
default:
case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
- case CollectionViewType.Docking: return <CollectionDockingView 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} />;
case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />;
case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />;
@@ -141,13 +141,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
};
setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) {
- if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
+ if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.dataDoc.isGroup && !this.Document.annotationOn) {
// prettier-ignore
const subItems: ContextMenuProps[] = [
{ 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: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'},
{ 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' },
@@ -172,26 +173,26 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
// need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
!Doc.noviceMode &&
this.setupViewTypes('Appearance...', vtype => {
- const newRendition = Doc.MakeEmbedding(this.rootDoc);
+ const newRendition = Doc.MakeEmbedding(this.Document);
newRendition._type_collection = vtype;
- this.props.addDocTab(newRendition, OpenWhere.addRight);
+ this._props.addDocTab(newRendition, OpenWhere.addRight);
return newRendition;
});
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null;
- if (this.rootDoc.childLayout instanceof Doc) {
- optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
+ !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;
+ 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.rootDoc.childClickedOpenTemplateView instanceof Doc) {
- optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView 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.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._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' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
- if (!Doc.noviceMode && !this.rootDoc.annotationOn) {
+ if (!Doc.noviceMode && !this.Document.annotationOn) {
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
const funcs = [
@@ -203,9 +204,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
description: `Edit ${func.name} script`,
icon: 'edit',
event: (obj: any) => {
- const embedding = Doc.MakeEmbedding(this.rootDoc);
+ const embedding = Doc.MakeEmbedding(this.Document);
DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
- this.props.addDocTab(embedding, OpenWhere.addRight);
+ this._props.addDocTab(embedding, OpenWhere.addRight);
},
})
);
@@ -213,51 +214,55 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
onClicks.push({
description: `Set child ${childClick.title}`,
icon: 'edit',
- event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
+ event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
})
);
- !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
if (!Doc.noviceMode) {
const more = cm.findByDescription('More...');
const moreItems = more && 'subitems' in more ? more.subitems : [];
- moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) });
+ moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.Document) });
!more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' });
}
}
};
- bodyPanelWidth = () => this.props.PanelWidth();
+ bodyPanelWidth = () => this._props.PanelWidth();
- childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
- childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
- childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
+ childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
isContentActive = (outsideReaction?: boolean) => this._isContentActive;
+ pointerEvents = () => {
+ const viewPath = this._props.DocumentView?.()?._props.docViewPath();
+ return (
+ this.layoutDoc._lockedPosition && //
+ viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform
+ );
+ };
+
render() {
TraceMobx();
+ const pointerEvents = this.pointerEvents() ? 'none' : undefined;
const props: SubCollectionViewProps = {
- ...this.props,
+ ...this._props,
addDocument: this.addDocument,
moveDocument: this.moveDocument,
removeDocument: this.removeDocument,
isContentActive: this.isContentActive,
isAnyChildContentActive: this.isAnyChildContentActive,
- whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
PanelWidth: this.bodyPanelWidth,
- PanelHeight: this.props.PanelHeight,
+ PanelHeight: this._props.PanelHeight,
ScreenToLocalTransform: this.screenToLocalTransform,
childLayoutTemplate: this.childLayoutTemplate,
- childLayoutString: StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString),
- childHideResizeHandles: this.childHideResizeHandles,
- childHideDecorationTitle: this.childHideDecorationTitle,
+ whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
+ childLayoutString: StrCast(this.Document.childLayoutString, this._props.childLayoutString),
+ childHideResizeHandles: this._props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
+ childHideDecorationTitle: this._props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
};
return (
- <div
- className="collectionView"
- onContextMenu={this.onContextMenu}
- style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
+ <div className="collectionView" onContextMenu={this.onContextMenu} style={{ pointerEvents }}>
{this.renderSubView(this.collectionViewType, props)}
</div>
);
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index d447991a1..dd4c0b881 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.tabDocView-content {
height: 100%;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 6e1e6cf8d..783817b06 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -2,38 +2,41 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, Type } from 'browndash-components';
import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx';
+import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
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 { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { FieldId } from '../../../fields/RefField';
+import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
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 { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoable, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoable } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
-import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
+import { Colors } from '../global/globalEnums';
import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
-import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
-import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
-import React = require('react');
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
const _global = (window /* browser */ || global) /* node */ as any;
interface TabDocViewProps {
@@ -42,10 +45,16 @@ interface TabDocViewProps {
glContainer: any;
}
@observer
-export class TabDocView extends React.Component<TabDocViewProps> {
+export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
static _allTabs = new ObservableSet<TabDocView>();
_mainCont: HTMLDivElement | null = null;
_tabReaction: IReactionDisposer | undefined;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable _activated: boolean = false;
@observable _panelWidth = 0;
@observable _panelHeight = 0;
@@ -55,21 +64,21 @@ export class TabDocView extends React.Component<TabDocViewProps> {
@computed get _isUserActivated() {
return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
}
- @computed get _isContentActive() {
+ get _isContentActive() {
return this._isUserActivated || this._hovering;
}
- @observable _document: Doc | undefined;
- @observable _view: DocumentView | undefined;
+ @observable _document: Doc | undefined = undefined;
+ @observable _view: DocumentView | undefined = undefined;
@computed get layoutDoc() {
return this._document && Doc.Layout(this._document);
}
get stack() {
- return (this.props as any).glContainer.parent.parent;
+ return this._props.glContainer.parent.parent;
}
get tab() {
- return (this.props as any).glContainer.tab;
+ return this._props.glContainer.tab;
}
get view() {
return this._view;
@@ -160,12 +169,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._isUserActivated
? 0
: this._hovering
- ? 0.25
- : degree === Doc.DocBrushStatus.selfBrushed
- ? 0.5
- : degree === Doc.DocBrushStatus.protoBrushed //
- ? 0.7
- : 0.9
+ ? 0.25
+ : degree === Doc.DocBrushStatus.selfBrushed
+ ? 0.5
+ : degree === Doc.DocBrushStatus.protoBrushed //
+ ? 0.7
+ : 0.9
)
.rgb()
.toString()
@@ -176,7 +185,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: MouseEvent) => {
- if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+ if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
tab.header.parent.setActiveContentItem(tab.contentItem);
tab.setActive(true);
}
@@ -259,10 +268,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
return;
}
const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
- const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
- pinDoc.presentation_targetDoc = anchorDoc ?? doc;
+ const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
+ const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc);
pinDoc.title = doc.title + ' - Slide';
- pinDoc.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
+ pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000;
pinDoc.presentation_groupWithUp = false;
@@ -271,7 +280,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.treeView = ''; // not really needed, but makes key value pane look better
pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
- pinDoc.treeView_ChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
@@ -315,7 +323,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
- @action
componentDidMount() {
new _global.ResizeObserver(
action((entries: any) => {
@@ -324,25 +331,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._panelHeight = entry.contentRect.height;
}
})
- ).observe(this.props.glContainer._element[0]);
- this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
- this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
+ ).observe(this._props.glContainer._element[0]);
+ this._props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
+ this._props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
// this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
// ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
// { fireImmediately: true });
- TabDocView._allTabs.add(this);
+ runInAction(() => TabDocView._allTabs.add(this));
}
- componentDidUpdate() {
+ componentDidUpdate(prevProps: Readonly<TabDocViewProps>) {
+ super.componentDidUpdate(prevProps);
this._view && DocumentManager.Instance.AddView(this._view);
}
- @action
componentWillUnmount() {
this._tabReaction?.();
this._view && DocumentManager.Instance.RemoveView(this._view);
- TabDocView._allTabs.delete(this);
+ runInAction(() => TabDocView._allTabs.delete(this));
- this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
+ this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
}
// Flag indicating that when a tab is activated, it should not select it's document.
@@ -377,9 +384,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
case undefined:
case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) {
const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
- const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)];
+ const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
if (lightboxView && (!data || data instanceof List)) {
- lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]);
+ lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]);
return true;
}
}
@@ -419,7 +426,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
- return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+ return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
};
PanelWidth = () => this._panelWidth;
PanelHeight = () => this._panelHeight;
@@ -440,10 +447,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._lastView = this._view;
})}
renderDepth={0}
- LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
- hideTitle={this.props.keyValue}
+ LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+ hideTitle={this._props.keyValue}
Document={this._document}
- DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
+ TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
onBrowseClick={DocumentView.exploreMode}
waitForDoubleClickToClick={this.waitForDoubleClick}
isContentActive={this.isContentActive}
@@ -459,7 +466,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
addDocTab={this.addDocTab}
ScreenToLocalTransform={this.ScreenToLocalTransform}
dontCenter={'y'}
- rootSelected={returnTrue}
whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
focus={this.focusFunc}
docViewPath={returnEmptyDoclist}
@@ -489,7 +495,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
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)));
+ 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);
}
}}>
@@ -513,14 +519,14 @@ interface TabMiniThumbProps {
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 React.Component<TabMinimapViewProps> {
+export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
if (doc) {
switch (property.split(':')[0]) {
@@ -547,8 +553,9 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
}
}
};
+
@computed get renderBounds() {
- const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView;
+ const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
const bounds = compView?.freeformData?.(true)?.bounds;
if (!bounds) return undefined;
const xbounds = bounds.r - bounds.x;
@@ -556,10 +563,10 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
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 };
}
- childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
+ 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 doc = this._props.document;
const miniSize = this.returnMiniSize();
doc &&
setupMoveUpEvents(
@@ -578,15 +585,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
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 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() }}>
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
<CollectionFreeFormView
- Document={this.props.document}
+ Document={this._props.document}
docViewPath={returnEmptyDoclist}
childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
noOverlay={true} // don't render overlay Docs since they won't scale
@@ -596,9 +603,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
select={emptyFunction}
isSelected={returnFalse}
dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this.props.document)}
+ fieldKey={Doc.LayoutFieldKey(this._props.document)}
bringToFront={emptyFunction}
- rootSelected={returnTrue}
addDocument={returnFalse}
moveDocument={returnFalse}
removeDocument={returnFalse}
@@ -609,7 +615,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
whenChildContentsActiveChanged={emptyFunction}
focus={emptyFunction}
styleProvider={TabMinimapView.miniStyleProvider}
- addDocTab={this.props.addDocTab}
+ addDocTab={this._props.addDocTab}
pinToPres={TabDocView.PinDoc}
childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
@@ -623,7 +629,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
);
};
render() {
- return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ 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.scss b/src/client/views/collections/TreeView.scss
index cbcc7c710..0a1946f09 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.treeView-label {
max-height: 1.5em;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 004857ed1..a7705ea7e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,8 +1,10 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton, Size } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+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, simulateMouseClick } from '../../../Utils';
import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -12,31 +14,29 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils, Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
-import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { StyleProp } from '../StyleProvider';
import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
+import { KeyValueBox } from '../nodes/KeyValueBox';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { KeyValueBox } from '../nodes/KeyValueBox';
-import { StyleProp } from '../StyleProvider';
import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import { TreeSort } from './TreeSort';
import './TreeView.scss';
-import React = require('react');
+const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -44,7 +44,7 @@ export interface TreeViewProps {
observeHeight: (ref: any) => void;
unobserveHeight: (ref: any) => void;
prevSibling?: Doc;
- document: Doc;
+ Document: Doc;
dataDoc?: Doc;
treeViewParent: Doc;
renderDepth: number;
@@ -87,7 +87,7 @@ const treeBulletWidth = function () {
* treeView_ExpandedView : name of field whose contents are being displayed as the document's subtree
*/
@observer
-export class TreeView extends React.Component<TreeViewProps> {
+export class TreeView extends ObservableReactComponent<TreeViewProps> {
static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
static _openTitleScript: Opt<ScriptField | undefined>;
static _openLevelScript: Opt<ScriptField | undefined>;
@@ -100,57 +100,61 @@ export class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
get treeViewOpenIsTransient() {
- return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc);
+ return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
}
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
- this.doc.treeView_Open = c;
+ this.Document.treeView_Open = c;
this._transientOpenState = false;
}
}
@observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state
@observable _editTitle: boolean = false;
- @observable _dref: DocumentView | undefined | null;
+ @observable _dref: DocumentView | undefined | null = undefined;
get displayName() {
- return 'TreeView(' + this.props.document.title + ')';
+ return 'TreeView(' + this.Document.title + ')';
} // this makes mobx trace() statements more descriptive
get defaultExpandedView() {
- return this.doc._type_collection === CollectionViewType.Docking
- ? this.fieldKey
- : this.props.treeView.dashboardMode
+ return this.Document._type_collection === CollectionViewType.Docking
? this.fieldKey
- : this.props.treeView.fileSysMode
- ? this.doc.isFolder
- ? this.fieldKey
- : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list
- : this.props.treeView.outlineMode || this.childDocs
- ? this.fieldKey
- : Doc.noviceMode
- ? 'layout'
- : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
+ : this.treeView.dashboardMode
+ ? this.fieldKey
+ : this.treeView.fileSysMode
+ ? this.Document.isFolder
+ ? this.fieldKey
+ : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list
+ : this.treeView.outlineMode || this.childDocs
+ ? this.fieldKey
+ : Doc.noviceMode
+ ? 'layout'
+ : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields');
+ }
+
+ @computed get treeView() {
+ return this._props.treeView;
}
- @computed get doc() {
- return this.props.document;
+ @computed get Document() {
+ return this._props.Document;
}
@computed get treeViewOpen() {
- return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
}
@computed get treeViewExpandedView() {
- return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView;
+ return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
}
@computed get MAX_EMBED_HEIGHT() {
- return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
+ return NumCast(this._props.treeViewParent.maxEmbedHeight, 200);
}
@computed get dataDoc() {
- return this.props.document.treeView_ChildrenOnRoot ? this.doc : this.doc[DocData];
+ return this.Document[DocData];
}
@computed get layoutDoc() {
- return Doc.Layout(this.doc);
+ return Doc.Layout(this.Document);
}
@computed get fieldKey() {
- return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc));
+ return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
}
@computed get childDocs() {
return this.childDocList(this.fieldKey);
@@ -169,30 +173,30 @@ export class TreeView extends React.Component<TreeViewProps> {
}
childDocList(field: string) {
- const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
- return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field])));
+ const layout = Cast(Doc.LayoutField(this.Document), Doc, null);
+ return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field])));
}
moving: boolean = false;
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- if (this.doc !== target && addDoc !== returnFalse) {
- const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.data)) instanceof ComputedField);
+ if (this.Document !== target && addDoc !== returnFalse) {
+ const canAdd1 = (this._props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
// bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
- if (canAdd1 && this.props.removeDoc?.(doc) === true) {
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true);
+ if (canAdd1 && this._props.removeDoc?.(doc) === true) {
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = true);
const res = addDoc(doc);
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = false);
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = false);
return res;
}
}
return false;
};
- @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
- this.props.treeView.props.select(false);
+ @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+ this.treeView._props.select(false);
const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
- res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
+ res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView._props.DocumentView?.())?.select(false);
return res;
};
@@ -200,10 +204,10 @@ export class TreeView extends React.Component<TreeViewProps> {
this._disposers.selection?.();
if (!docView) {
this._editTitle = false;
- } else if (docView.isSelected()) {
+ } else if (docView.SELECTED) {
this._editTitle = true;
this._disposers.selection = reaction(
- () => docView.isSelected(),
+ () => docView.SELECTED,
isSel => !isSel && this.setEditTitle(undefined)
);
} else {
@@ -212,17 +216,16 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@action
openLevel = (docView: DocumentView) => {
- if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
+ if (this.Document.isFolder || Doc.IsSystem(this.Document)) {
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.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc);
- this.props.addDocTab(bestEmbedding, OpenWhere.lightbox);
+ const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+ this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
}
};
@undoBatch
- @action
recurToggle = (childList: Doc[]) => {
if (childList.length > 0) {
childList.forEach(child => {
@@ -233,7 +236,6 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@undoBatch
- @action
getRunningChildren = (childList: Doc[]) => {
if (childList.length === 0) {
return [];
@@ -253,23 +255,24 @@ export class TreeView extends React.Component<TreeViewProps> {
static GetRunningChildren = new Map<Doc, any>();
static ToggleChildrenRun = new Map<Doc, () => void>();
- constructor(props: any) {
+ constructor(props: TreeViewProps) {
super(props);
+ makeObservable(this);
if (!TreeView._openLevelScript) {
TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' });
TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' });
}
- this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!;
- this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
+ this._openScript = Doc.IsSystem(this.Document) ? undefined : () => TreeView._openLevelScript!;
+ this._editTitleScript = Doc.IsSystem(this.Document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
// set for child processing highligting
this.dataDoc.hasChildren = this.childDocs.length > 0;
// this.dataDoc.children = this.childDocs;
- TreeView.ToggleChildrenRun.set(this.doc, () => {
+ TreeView.ToggleChildrenRun.set(this.Document, () => {
this.recurToggle(this.childDocs);
});
- TreeView.GetRunningChildren.set(this.doc, () => {
+ TreeView.GetRunningChildren.set(this.Document, () => {
return this.getRunningChildren(this.childDocs);
});
}
@@ -277,31 +280,32 @@ export class TreeView extends React.Component<TreeViewProps> {
_treeEle: any;
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc);
- if (this._treeEle) this.props.unobserveHeight(this._treeEle);
- this.props.observeHeight((this._treeEle = ele));
+ ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.Document);
+ if (this._treeEle) this._props.unobserveHeight(this._treeEle);
+ this._props.observeHeight((this._treeEle = ele));
};
componentWillUnmount() {
this._renderTimer && clearTimeout(this._renderTimer);
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._treeEle && this.props.unobserveHeight(this._treeEle);
+ this._treeEle && this._props.unobserveHeight(this._treeEle);
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointermove', this.onDragUp, true);
// TODO: [AL] add these
- this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex);
}
- componentDidUpdate() {
+ componentDidUpdate(prevProps: Readonly<TreeViewProps>) {
+ super.componentDidUpdate(prevProps);
this._disposers.opening = reaction(
() => this.treeViewOpen,
open => !open && (this._renderCount = 20)
);
- this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
componentDidMount() {
- this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
onDragUp = (e: PointerEvent) => {
@@ -309,8 +313,8 @@ export class TreeView extends React.Component<TreeViewProps> {
document.removeEventListener('pointermove', this.onDragMove, true);
};
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
- if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+ this._props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
+ if (e.buttons === 1 && SnappingManager.IsDragging && this._props.isContentActive()) {
this._header.current!.className = 'treeView-header';
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointerup', this.onDragUp, true);
@@ -333,7 +337,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length);
this._header.current!.className = 'treeView-header';
- if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) {
+ if (!this.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.treeView.Document) {
if (inside) this._header.current!.className += ' treeView-header-inside';
else if (before) this._header.current!.className += ' treeView-header-above';
else if (!before) this._header.current!.className += ' treeView-header-below';
@@ -361,7 +365,7 @@ export class TreeView extends React.Component<TreeViewProps> {
_width: 1000,
_height: 10,
});
- Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
+ Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text');
Doc.GetProto(bullet).data = new List<Doc>([]);
DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
@@ -371,18 +375,18 @@ export class TreeView extends React.Component<TreeViewProps> {
makeTextCollection = () => {
const bullet = TreeView.makeTextBullet();
TreeView._editTitleOnLoad = { id: bullet[Id], parent: this };
- return this.props.addDocument(bullet);
+ return this._props.addDocument(bullet);
};
makeFolder = () => {
const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
- TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
- return this.props.addDocument(folder);
+ TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView };
+ return this._props.addDocument(folder);
};
preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
const dragData = de.complete.docDragData;
- dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
+ dragData && (dragData.dropAction = this.treeView.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
};
@undoBatch
@@ -391,17 +395,17 @@ export class TreeView extends React.Component<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.props.treeView.fileSysMode && !this.doc.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 ? true : false);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
- const destDoc = this.doc;
+ const destDoc = this.Document;
DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' });
e.stopPropagation();
return true;
}
const docDragData = de.complete.docDragData;
if (docDragData && pt[0] < rect.left + rect.width) {
- if (docDragData.draggedDocuments[0] === this.doc) return true;
+ if (docDragData.draggedDocuments[0] === this.Document) return true;
const added = this.dropDocuments(
docDragData.droppedDocuments, //
before,
@@ -409,7 +413,7 @@ export class TreeView extends React.Component<TreeViewProps> {
docDragData.dropAction,
docDragData.removeDocument,
docDragData.moveDocument,
- docDragData.treeViewDoc === this.props.treeView.props.Document
+ docDragData.treeViewDoc === this.treeView.Document
);
e.stopPropagation();
!added && e.preventDefault();
@@ -420,40 +424,40 @@ export class TreeView extends React.Component<TreeViewProps> {
dropping: boolean = false;
dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
- const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before);
+ const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
const localAdd = (doc: Doc | Doc[]) => {
const innerAdd = (doc: Doc) => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
- dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+ 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);
};
const addDoc = inside ? localAdd : parentAddDoc;
const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
- if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) {
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
+ const canAdd = (!this.treeView.outlineMode && !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
+ if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
return res;
}
return false;
}
refTransform = (ref: HTMLDivElement | undefined | null) => {
- if (!ref) return this.props.ScreenToLocalTransform();
+ if (!ref) return this._props.ScreenToLocalTransform();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
- const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle());
- const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+ const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+ const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]);
};
docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
getTransform = () => this.refTransform(this._tref.current);
- embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
+ embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1);
embeddedPanelHeight = () => {
- const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc;
return Math.min(
NumCast(layoutDoc._height),
this.MAX_EMBED_HEIGHT,
@@ -463,7 +467,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return layoutDoc._layout_fitWidth
? !Doc.NativeHeight(layoutDoc)
? NumCast(layoutDoc._height)
- : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
+ : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this._props.treeViewParent._height)))
: (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
})()
);
@@ -471,16 +475,16 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get expandedField() {
const ids: { [key: string]: string } = {};
const rows: JSX.Element[] = [];
- const doc = this.doc;
+ const doc = this.Document;
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
for (const key of Object.keys(ids).slice().sort()) {
- if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') 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 expandedWidth = () => this.props.panelWidth() - leftOffset.width;
+ 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);
@@ -488,44 +492,44 @@ export class TreeView extends React.Component<TreeViewProps> {
const innerAdd = (doc: Doc) => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+ 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);
};
contentElement = TreeView.GetChildElements(
contents instanceof Doc ? [contents] : DocListCast(contents),
- this.props.treeView,
+ this.treeView,
this,
doc,
undefined,
- this.props.treeViewParent,
- this.props.prevSibling,
+ this._props.treeViewParent,
+ this._props.prevSibling,
addDoc,
remDoc,
moveDoc,
- this.props.dragAction,
- this.props.addDocTab,
+ this._props.dragAction,
+ this._props.addDocTab,
this.titleStyleProvider,
- this.props.ScreenToLocalTransform,
- this.props.isContentActive,
+ this._props.ScreenToLocalTransform,
+ this._props.isContentActive,
expandedWidth,
- this.props.renderDepth,
- this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, doc[Id]],
- this.props.onCheckedClick,
- this.props.onChildClick,
- this.props.skipFields,
+ this._props.renderDepth,
+ this._props.treeViewHideHeaderFields,
+ [...this._props.renderedIds, doc[Id]],
+ this._props.onCheckedClick,
+ this._props.onChildClick,
+ this._props.skipFields,
false,
- this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView,
+ this._props.whenChildContentsActiveChanged,
+ this._props.dontRegisterView,
emptyFunction,
emptyFunction,
this.childContextMenuItems(),
// TODO: [AL] Add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex
);
} else {
contentElement = (
@@ -573,9 +577,9 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
- const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
+ const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
+ const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
const sortKeys = Object.keys(sortings);
const curSortIndex = Math.max(
0,
@@ -587,7 +591,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
- const ordering = StrCast(this.doc.treeView_SortCriterion);
+ const ordering = StrCast(this.Document.treeView_SortCriterion);
if (ordering === TreeSort.Zindex) {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
@@ -596,7 +600,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- !dataIsComputed && added && Doc.SetContainer(doc, this.doc);
+ !dataIsComputed && added && Doc.SetContainer(doc, this.Document);
return added;
};
@@ -614,12 +618,12 @@ export class TreeView extends React.Component<TreeViewProps> {
}
return (
<div>
- {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
+ {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
<div className="treeView-sorting">
<IconButton
color={sortings[sorting]?.color}
size={Size.XSMALL}
- tooltip={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+ tooltip={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
icon={sortings[sorting]?.icon}
onPointerDown={e => {
downX = e.clientX;
@@ -627,8 +631,8 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
onClick={undoable(e => {
- if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
}, 'sort order')}
@@ -638,7 +642,7 @@ export class TreeView extends React.Component<TreeViewProps> {
<ul
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
- title={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+ title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
@@ -646,8 +650,8 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
onClick={undoable(e => {
- if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
}, 'sort order')}>
@@ -655,37 +659,37 @@ export class TreeView extends React.Component<TreeViewProps> {
? null
: TreeView.GetChildElements(
docs,
- this.props.treeView,
+ this.treeView,
this,
this.layoutDoc,
this.dataDoc,
- this.props.treeViewParent,
- this.props.prevSibling,
+ this._props.treeViewParent,
+ this._props.prevSibling,
addDoc,
remDoc,
moveDoc,
- StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType,
- this.props.addDocTab,
+ StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType,
+ this._props.addDocTab,
this.titleStyleProvider,
- this.props.ScreenToLocalTransform,
- this.props.isContentActive,
- this.props.panelWidth,
- this.props.renderDepth,
- this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, this.doc[Id]],
- this.props.onCheckedClick,
- this.props.onChildClick,
- this.props.skipFields,
+ this._props.ScreenToLocalTransform,
+ this._props.isContentActive,
+ this._props.panelWidth,
+ this._props.renderDepth,
+ this._props.treeViewHideHeaderFields,
+ [...this._props.renderedIds, this.Document[Id]],
+ this._props.onCheckedClick,
+ this._props.onChildClick,
+ this._props.skipFields,
false,
- this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView,
+ this._props.whenChildContentsActiveChanged,
+ this._props.dontRegisterView,
emptyFunction,
emptyFunction,
this.childContextMenuItems(),
// TODO: [AL] add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex,
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex,
this._renderCount
)}
</ul>
@@ -693,7 +697,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
} else if (this.treeViewExpandedView === 'fields') {
return (
- <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
+ <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
<div>{this.expandedField}</div>
</ul>
);
@@ -705,13 +709,13 @@ export class TreeView extends React.Component<TreeViewProps> {
e.preventDefault();
e.stopPropagation();
}}>
- {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
+ {this.renderEmbeddedDocument(false, this.treeView._props.childDocumentsActive ?? returnFalse)}
</ul>
); // "layout"
}
get onCheckedClick() {
- return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick);
+ return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick);
}
@action
@@ -719,10 +723,10 @@ export class TreeView extends React.Component<TreeViewProps> {
if (this.onCheckedClick) {
this.onCheckedClick?.script.run(
{
- this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
- heading: this.props.treeViewParent.title,
- checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check',
- containingTreeView: this.props.treeView.props.Document,
+ this: this.Document.isTemplateForField && this._props.dataDoc ? this._props.dataDoc : this.Document,
+ heading: this._props.treeViewParent.title,
+ checked: this.Document.treeView_Checked === 'check' ? 'x' : this.Document.treeView_Checked === 'x' ? 'remove' : 'check',
+ containingTreeView: this.treeView.Document,
},
console.log
);
@@ -734,28 +738,28 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderBullet() {
TraceMobx();
- const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
+ const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
const color = SettingsManager.userColor;
- const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined;
+ const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined;
return (
<div
- className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
+ className={`bullet${this.treeView.outlineMode ? '-outline' : ''}`}
key="bullet"
- title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
+ title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.Document.type} content`}
onClick={this.bulletClick}
style={
- this.props.treeView.outlineMode
+ this.treeView.outlineMode
? {
- opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity),
+ opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity),
}
: {
- pointerEvents: this.props.isContentActive() ? 'all' : undefined,
+ pointerEvents: this._props.isContentActive() ? 'all' : undefined,
opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4,
color: checked === 'unchecked' ? SettingsManager.userColor : 'inherit',
}
}>
- {this.props.treeView.outlineMode ? (
- !(this.doc.text as RichTextField)?.Text ? null : (
+ {this.treeView.outlineMode ? (
+ !(this.Document.text as RichTextField)?.Text ? null : (
<IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} />
)
) : (
@@ -775,28 +779,28 @@ export class TreeView extends React.Component<TreeViewProps> {
}
@computed get validExpandViewTypes() {
- const annos = () => (DocListCast(this.doc[this.fieldKey + '_annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
- const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : '');
- const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
- const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings');
+ const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : '');
+ const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : '');
+ const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : '');
+ const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings');
const fields = () => (Doc.noviceMode ? '' : 'fields');
- const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout'];
- return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
+ const layout = Doc.noviceMode || this.Document._type_collection === CollectionViewType.Docking ? [] : ['layout'];
+ return [data(), ...layout, ...(this.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
}
@action
expandNextviewType = () => {
- if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) {
+ if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) {
const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length];
- this.doc.treeView_ExpandedView = next(this.validExpandViewTypes);
+ this.Document.treeView_ExpandedView = next(this.validExpandViewTypes);
}
this.treeViewOpen = true;
};
@observable headerEleWidth = 0;
@computed get titleButtons() {
- const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
+ const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations);
const color = SettingsManager.userColor;
- return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : (
+ return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : (
<>
{customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
<IconButton
@@ -808,7 +812,7 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
/>
- {Doc.noviceMode ? null : this.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
+ {Doc.noviceMode ? null : this.Document.treeView_ExpandedViewLock || Doc.IsSystem(this.Document) ? null : (
<span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
{this.treeViewExpandedView}
</span>
@@ -825,54 +829,54 @@ export class TreeView extends React.Component<TreeViewProps> {
contextMenuItems = () => {
const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
const folderOp = this.childDocs?.length ? [makeFolder] : [];
- const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
- const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
- const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Reopen' };
+ const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
+ const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' };
+ const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' };
return [
- ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
- ...(this.doc.isFolder
+ ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)),
+ ...(this.Document.isFolder
? folderOp
- : Doc.IsSystem(this.doc)
- ? []
- : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
- ? [openEmbedding, makeFolder]
- : this.doc._type_collection === CollectionViewType.Docking
- ? []
- : this.props.treeView.rootDoc === Doc.MyRecentlyClosed
- ? [reopenDoc]
- : [openEmbedding, focusDoc]),
+ : Doc.IsSystem(this.Document)
+ ? []
+ : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document)
+ ? [openEmbedding, makeFolder]
+ : this.Document._type_collection === CollectionViewType.Docking
+ ? []
+ : this.treeView.Document === Doc.MyRecentlyClosed
+ ? [reopenDoc]
+ : [openEmbedding, focusDoc]),
];
};
childContextMenuItems = () => {
- const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- const icons = StrListCast(this.doc.childContextMenuIcons);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.Document.childContextMenuIcons);
+ return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
+ onChildClick = () => this._props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!);
- onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
+ onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null);
- refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {});
+ refocus = () => this.treeView._props.focus(this.treeView.Document, {});
ignoreEvent = (e: any) => {
- if (this.props.isContentActive(true)) {
+ if (this._props.isContentActive(true)) {
e.stopPropagation();
e.preventDefault();
}
};
titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
- if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+ 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.props.treeView;
+ const treeView = this.treeView;
// prettier-ignore
switch (property.split(':')[0]) {
- case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1;
+ case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
- case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined;
+ case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
- const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
+ 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 : (
<div
@@ -881,33 +885,33 @@ export class TreeView extends React.Component<TreeViewProps> {
// just render a title for a tree view label (identified by treeViewDoc being set in 'props')
maxWidth: props?.PanelWidth() || undefined,
background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
- outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`,
- paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
- paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
- paddingTop: treeView.props.childYPadding,
- paddingBottom: treeView.props.childYPadding,
+ outline: SnappingManager.IsDragging ? undefined: `solid ${highlightColor} ${highlightIndex}px`,
+ paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+ paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+ paddingTop: treeView._props.childYPadding,
+ paddingBottom: treeView._props.childYPadding,
}}>
{StrCast(doc?.title)}
</div>
);
}
- return treeView.props.styleProvider?.(doc, props, property);
+ return treeView._props.styleProvider?.(doc, props, property);
};
embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (property.startsWith(StyleProp.Decorations)) return null;
- return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+ return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
- if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
+ if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
e.stopPropagation?.();
e.preventDefault?.();
setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
- UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab');
+ UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab');
return true;
case 'Backspace':
- if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) {
+ if (!(this.Document.text as RichTextField)?.Text && this._props.removeDoc?.(this.Document)) {
e.stopPropagation?.();
e.preventDefault?.();
return true;
@@ -921,7 +925,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
return false;
};
- titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
+ titleWidth = () => Math.max(20, Math.min(this.treeView.truncateTitleWidth(), this._props.panelWidth())) / (this.treeView._props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
/**
* Renders the EditableView title element for placement into the tree.
@@ -936,21 +940,21 @@ export class TreeView extends React.Component<TreeViewProps> {
display={'inline-block'}
editing={this._editTitle}
background={'#7089bb'}
- contents={StrCast(this.doc.title)}
+ contents={StrCast(this.Document.title)}
height={12}
sizeToContent={true}
fontSize={12}
isEditingCallback={action(e => (this._editTitle = e))}
- GetValue={() => StrCast(this.doc.title)}
+ GetValue={() => StrCast(this.Document.title)}
OnTab={undoBatch((shift?: boolean) => {
- if (!shift) this.props.indentDocument?.(true);
- else this.props.outdentDocument?.(true);
+ if (!shift) this._props.indentDocument?.(true);
+ else this._props.outdentDocument?.(true);
})}
- OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))}
- OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()}
+ OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
+ OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
- Doc.SetInPlace(this.doc, 'title', value, false);
- this.props.treeView.outlineMode && enterKey && this.makeTextCollection();
+ Doc.SetInPlace(this.Document, 'title', value, false);
+ this.treeView.outlineMode && enterKey && this.makeTextCollection();
})}
/>
) : (
@@ -958,49 +962,46 @@ export class TreeView extends React.Component<TreeViewProps> {
key="title"
ref={action((r: any) => {
this._docRef = r ? r : undefined;
- if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) {
+ if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
this._docRef.select(false);
this.setEditTitle(this._docRef);
TreeView._editTitleOnLoad = undefined;
}
})}
- Document={this.doc}
- DataDoc={undefined} // or this.dataDoc?
+ Document={this.Document}
layout_fitWidth={returnTrue}
scriptContext={this}
- hideDecorationTitle={this.props.treeView.outlineMode}
- hideResizeHandles={this.props.treeView.outlineMode}
+ hideDecorationTitle={this.treeView.outlineMode}
+ hideResizeHandles={this.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
- docViewPath={this.props.treeView.props.docViewPath}
- treeViewDoc={this.props.treeView.props.Document}
+ docViewPath={this.treeView._props.docViewPath}
+ treeViewDoc={this.treeView.Document}
addDocument={undefined}
- addDocTab={this.props.addDocTab}
- rootSelected={returnTrue}
- pinToPres={emptyFunction}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this.treeView._props.pinToPres}
onClick={this.onChildClick}
onDoubleClick={this.onChildDoubleClick}
- dragAction={this.props.dragAction}
+ dragAction={this._props.dragAction}
moveDocument={this.move}
- removeDocument={this.props.removeDoc}
+ removeDocument={this._props.removeDoc}
ScreenToLocalTransform={this.getTransform}
- NativeHeight={return18}
+ NativeHeight={returnZero}
NativeWidth={returnZero}
- shouldNotScale={returnTrue}
PanelWidth={this.titleWidth}
PanelHeight={return18}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- isContentActive={emptyFunction} //this.props.isContentActive}
- isDocumentActive={this.props.isContentActive}
+ isContentActive={emptyFunction} //this._props.isContentActive}
+ isDocumentActive={this._props.isContentActive}
focus={this.refocus}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
bringToFront={emptyFunction}
- disableBrushing={this.props.treeView.props.disableBrushing}
- hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
- xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
- yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
+ disableBrushing={this.treeView._props.disableBrushing}
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+ xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+ yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
@@ -1009,16 +1010,16 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<>
<div
- className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`}
+ className={`docContainer${Doc.IsSystem(this.Document) || this.Document.isFolder ? '-system' : ''}`}
ref={this._tref}
title="click to edit title. Double Click or Drag to Open"
style={{
- backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? SettingsManager.userVariantColor : undefined,
- color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
- fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
- textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
- outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
- pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
+ backgroundColor: Doc.IsSystem(this.Document) || this.Document.isFolder ? SettingsManager.userVariantColor : undefined,
+ color: Doc.IsSystem(this.Document) || this.Document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
+ fontWeight: Doc.IsSearchMatch(this.Document) !== undefined ? 'bold' : undefined,
+ textDecoration: Doc.GetT(this.Document, 'title', 'string', true) ? 'underline' : undefined,
+ outline: this.Document === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
+ pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
}}>
{view}
</div>
@@ -1058,44 +1059,42 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
<DocumentView
- key={this.doc[Id]}
+ key={this.Document[Id]}
ref={action((r: DocumentView | null) => (this._dref = r))}
- Document={this.doc}
- DataDoc={undefined}
+ Document={this.Document}
layout_fitWidth={this.fitWidthFilter}
PanelWidth={this.embeddedPanelWidth}
PanelHeight={this.embeddedPanelHeight}
LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
- LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+ LayoutTemplate={this.treeView._props.childLayoutTemplate}
isContentActive={isActive}
isDocumentActive={isActive}
styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
hideTitle={asText}
- //fitContentsToBox={returnTrue}
- hideDecorationTitle={this.props.treeView.outlineMode}
- hideResizeHandles={this.props.treeView.outlineMode}
+ fitContentsToBox={returnTrue}
+ hideDecorationTitle={this.treeView.outlineMode}
+ hideResizeHandles={this.treeView.outlineMode}
onClick={this.onChildClick}
focus={this.refocus}
onKey={this.onKeyDown}
- hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
ScreenToLocalTransform={this.docTransform}
- renderDepth={this.props.renderDepth + 1}
- treeViewDoc={this.props.treeView?.props.Document}
- rootSelected={returnTrue}
- docViewPath={this.props.treeView.props.docViewPath}
+ renderDepth={this._props.renderDepth + 1}
+ treeViewDoc={this.treeView?.Document}
+ docViewPath={this.treeView._props.docViewPath}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- addDocument={this.props.addDocument}
+ addDocument={this._props.addDocument}
moveDocument={this.move}
- removeDocument={this.props.removeDoc}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
- yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.treeView.props.pinToPres}
- disableBrushing={this.props.treeView.props.disableBrushing}
+ 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}
bringToFront={returnFalse}
scriptContext={this}
/>
@@ -1105,7 +1104,7 @@ export class TreeView extends React.Component<TreeViewProps> {
// renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views.
@computed get renderTitleAsHeader() {
- return this.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? (
+ return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
<div></div>
) : (
<>
@@ -1120,16 +1119,16 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<>
{this.renderBullet}
- {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
+ {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
</>
);
};
@computed get renderBorder() {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
- const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
+ const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
+ const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
return (
- <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
+ <div className={`treeView-border${this.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
{!this.treeViewOpen ? null : this.renderContent}
</div>
);
@@ -1139,28 +1138,28 @@ export class TreeView extends React.Component<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.props.treeView.fileSysMode && !this.doc.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 ? true : false);
- const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
+ const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
};
render() {
TraceMobx();
- const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
- return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? (
- '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
+ const hideTitle = this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode;
+ return this._props.renderedIds?.indexOf(this.Document[Id]) !== -1 ? (
+ '<' + this.Document.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
) : (
<div
- className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`}
+ 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">
- {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
+ {hideTitle && this.Document.type !== DocumentType.RTF && !this.Document.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
? this.renderEmbeddedDocument(false, returnFalse)
- : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
+ : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.Document.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
</li>
</div>
);
@@ -1236,7 +1235,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
- const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
+ const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
const treeView_Refs = new Map<Doc, TreeView | undefined>();
return docs
.filter(child => child instanceof Doc)
@@ -1248,11 +1247,11 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
- if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
+ if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return;
const fieldKey = Doc.LayoutFieldKey(newParent);
if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
remove(child);
- FormattedTextBox.SelectOnLoad = child[Id];
+ FormattedTextBox.SetSelectOnLoad(child);
TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
newParent.treeView_Open = true;
@@ -1260,7 +1259,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
- const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+ const 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);
const rowHeight = () => {
@@ -1271,7 +1270,7 @@ export class TreeView extends React.Component<TreeViewProps> {
<TreeView
key={child[Id]}
ref={r => treeView_Refs.set(child, r ? r : undefined)}
- document={pair.layout}
+ Document={pair.layout}
dataDoc={pair.data}
treeViewParent={treeView_Parent}
prevSibling={docs[i]}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 00505dbe3..99ee5ef4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -2,7 +2,7 @@ import { observer } from 'mobx-react';
import { Doc } from '../../../../fields/Doc';
import { NumCast } from '../../../../fields/Types';
import './CollectionFreeFormView.scss';
-import React = require('react');
+import * as React from 'react';
export interface CollectionFreeFormViewBackgroundGridProps {
panX: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
new file mode 100644
index 000000000..d9f7ca6ea
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -0,0 +1,113 @@
+import { IconButton, Size, Type } from 'browndash-components';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import './CollectionFreeFormView.scss';
+// import assets from './assets/link.png';
+
+/**
+ * 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
+ */
+export type infoArc = [() => any, (res?: any) => infoState];
+
+export const StateMessage = Symbol('StateMessage');
+export const StateEntryFunc = Symbol('StateEntryFunc');
+export const StateMessageGIF = Symbol('StateMessageGIF');
+export class infoState {
+ [StateMessage]: string = '';
+ [StateEntryFunc]?: () => any;
+ [StateMessageGIF]?: string = '';
+ [key: string]: infoArc;
+ constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) {
+ this[StateMessage] = message;
+ Object.assign(this, arcs);
+ this[StateEntryFunc] = entryFunc;
+ this[StateMessageGIF] = messageGif;
+ }
+}
+
+/**
+ * Create an FSA state.
+ * @param msg the message displayed when in this state
+ * @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and
+ * field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire),
+ * and an arc transition action function (that sets the next state)
+ * @param entryFunc a function to call when entering the state
+ * @returns an FSA state
+ */
+export function InfoState(
+ msg: string, //
+ arcs: { [key: string]: infoArc },
+ entryFunc?: () => any,
+ gif?: string
+) {
+ return new infoState(msg, arcs, entryFunc, gif);
+}
+
+export interface CollectionFreeFormInfoStateProps {
+ infoState: infoState;
+ next: (state: infoState) => any;
+}
+
+@observer
+export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> {
+ _disposers: IReactionDisposer[] = [];
+ @observable _hide = false;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ get State() {
+ return this._props.infoState;
+ }
+ get Arcs() {
+ return Object.keys(this.State ?? []).map(key => this.State?.[key]);
+ }
+
+ clearState = () => this._disposers.map(disposer => disposer());
+ initState = () =>
+ (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => {
+ return reaction(
+ //
+ arc.test,
+ res => {
+ if (res) {
+ const next = arc.act(res);
+ this._props.next(next);
+ }
+ },
+ { fireImmediately: true }
+ );
+ }));
+
+ componentDidMount(): void {
+ this.initState();
+ }
+ componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) {
+ super.componentDidUpdate(prevProps);
+ this.clearState();
+ this.initState();
+ }
+ componentWillUnmount(): void {
+ this.clearState();
+ }
+ render() {
+ return (
+ <div className="collectionFreeform-infoUI" style={{display:this._hide ? 'none':undefined}}>
+ <div className="msg">{this.State?.[StateMessage]}</div>
+ <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}>
+ <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img>
+ </div>
+ <div style={{position:"absolute", top:-10, left:-10}}>
+ <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this._hide = true)} />
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
new file mode 100644
index 000000000..181f325f3
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -0,0 +1,230 @@
+import { IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { LinkManager } from '../../../util/LinkManager';
+import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { CollectionFreeFormInfoState, InfoState, infoState, StateEntryFunc } from './CollectionFreeFormInfoState';
+import { CollectionFreeFormView } from './CollectionFreeFormView';
+import './CollectionFreeFormView.scss';
+
+export interface CollectionFreeFormInfoUIProps {
+ Document: Doc;
+ Freeform: CollectionFreeFormView;
+}
+
+@observer
+export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
+ _firstDocPos = { x: 0, y: 0 };
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ this.currState = this.setupStates();
+ }
+
+ @observable _currState: infoState | undefined = undefined;
+ get currState() { return this._currState!; } // prettier-ignore
+ set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
+
+ setCurrState = (state: infoState) => {
+ if (state) {
+ this.currState = state;
+ this.currState[StateEntryFunc]?.();
+ }
+ };
+
+ setupStates = () => {
+ // state entry functions
+ const setBackground = (colour: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = colour);
+ const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
+ // arc transition trigger conditions
+ const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
+ const numDocs = () => this._props.Freeform.childDocs.length;
+
+ let docX: FieldResult<Field>;
+ let docY: FieldResult<Field>;
+
+ const docNewX = () => firstDoc()?.x;
+ const docNewY = () => firstDoc()?.y;
+
+ const linkStart = () => DocumentLinksButton.StartLink;
+
+ const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
+ const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
+
+ const activeTool = () => Doc.ActiveTool;
+
+ const pin = () => DocListCast(Doc.ActivePresentation?.data);
+
+ let trail: number;
+
+ const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails);
+ const presentationMode = () => Doc.ActivePresentation?.presentation_status;
+
+ // set of states
+ const start = InfoState('Click anywhere and begin typing to create your first text document.', {
+ docCreated: [() => numDocs(), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return oneDoc;
+ }],
+ }, setBackground("blue")); // prettier-ignore
+
+ const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', {
+ // docCreated: [() => numDocs() > 1, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc1;
+ }],
+ }, setBackground("red")); // prettier-ignore
+
+ const movedDoc1 = InfoState('Great moves. Try creating a second document.', {
+ docCreated: [() => numDocs() == 2, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc2;
+ }],
+ }, setBackground("yellow")); // prettier-ignore
+
+ const movedDoc2 = InfoState('Slick moves. Try creating a second document.', {
+ docCreated: [() => numDocs() == 2, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc3;
+ }],
+ }, setBackground("pink")); // prettier-ignore
+
+ const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', {
+ docCreated: [() => numDocs() == 2, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc1;
+ }],
+ }, setBackground("green")); // prettier-ignore
+
+ const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', {
+ linkStarted: [() => linkStart(), () => startedLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ }, setBackground("purple")); // prettier-ignore
+
+ const startedLink = InfoState('Now click the highlighted link icon on your other document.', {
+ linkCreated: [() => numDocLinks(), () => madeLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ }, setBackground("orange")); // prettier-ignore
+
+ const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', {
+ linkCreated: [() => !numDocLinks(), () => multipleDocs],
+ linkViewed: [() => linkMenuOpen(), () => {
+ alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+ return viewedLink;
+ }],
+ }, setBackground("blue")); // prettier-ignore
+
+ const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', {
+ linkDeleted: [() => !numDocLinks(), () => multipleDocs],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ docCreated: [() => numDocs() == 3, () => {
+ trail = pin().length;
+ return presentDocs;
+ }],
+ activePen: [() => activeTool() === InkTool.Pen, () => penMode],
+ }, setBackground("black")); // prettier-ignore
+
+ const presentDocs = InfoState('Another document! You could make a presentation. Click the pin icon in the top left corner.', {
+ docPinned: [() => pin().length > trail, () => {
+ trail = pin().length;
+ return pinnedDoc1;
+ }],
+ docRemoved: [() => numDocs() < 3, () => viewedLink],
+ }, setBackground("black"), "/assets/dash-pin-with-view.gif");
+
+ const penMode = InfoState('You\'re in pen mode. Click and drag to draw your first masterpiece.', {
+ // activePen: [() => activeTool() === InkTool.Eraser, () => eraserMode],
+ activePen: [() => activeTool() !== InkTool.Pen, () => viewedLink],
+ }); // prettier-ignore
+
+ // const eraserMode = InfoState('You\'re in eraser mode. Say goodbye to your first masterpiece.', {
+ // docsRemoved: [() => numDocs() == 3, () => demos],
+ // }); // prettier-ignore
+
+ const pinnedDoc1 = InfoState('You just pinned your doc.', {
+ docPinned: [() => pin().length > trail, () => {
+ trail = pin().length;
+ return pinnedDoc2;
+ }],
+ // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
+ docRemoved: [() => numDocs() < 3, () => viewedLink],
+ });
+
+ const pinnedDoc2 = InfoState(`You pinned another doc.`, {
+ docPinned: [() => pin().length > trail, () => {
+ trail = pin().length;
+ return pinnedDoc3;
+ }],
+ // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
+ docRemoved: [() => numDocs() < 3, () => viewedLink],
+ });
+
+ const pinnedDoc3 = InfoState(`You pinned yet another doc.`, {
+ docPinned: [() => pin().length > trail, () => {
+ trail = pin().length;
+ return pinnedDoc2;
+ }],
+ // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
+ docRemoved: [() => numDocs() < 3, () => viewedLink],
+ });
+
+ // const openedTrail = InfoState('This is your trails tab.', {
+ // trailView: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // });
+
+ // const editPresentationMode = InfoState('You are editing your presentation.', {
+ // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
+ // docRemoved: [() => numDocs() < 3, () => demos],
+ // docCreated: [() => numDocs() == 4, () => completed],
+ // });
+
+ const manualPresentationMode = InfoState('You\'re in manual presentation mode.', {
+ // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
+ docRemoved: [() => numDocs() < 3, () => viewedLink],
+ 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],
+ });
+
+ const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', {
+ docRemoved: [() => numDocs() == 1, () => oneDoc],
+ }, setBackground("white")); // prettier-ignore
+
+ return start;
+ };
+
+ render() {
+ return <CollectionFreeFormInfoState next={this.setCurrState} infoState={this.currState} />;
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 403fba67b..b8c0967c1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,7 @@ import { RefField } from '../../../../fields/RefField';
import { listSpec } from '../../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { aggregateBounds } from '../../../../Utils';
-import React = require('react');
+import * as React from 'react';
export interface ViewDefBounds {
type: string;
@@ -37,6 +37,7 @@ export interface PoolData {
zIndex?: number;
width?: number;
height?: number;
+ autoDim?: number; // 1 for set to Panel dims, 0 for use width/height as entered
backgroundColor?: string;
color?: string;
opacity?: number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index aca6df3c9..6337c8d34 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,5 +1,6 @@
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, Field } from '../../../../fields/Doc';
import { Brushed, DocCss } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -12,8 +13,8 @@ import { SettingsManager } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../../nodes/DocumentView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormLinkView.scss';
-import React = require('react');
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -24,24 +25,29 @@ export interface CollectionFreeFormLinkViewProps {
// props.screentolocatransform
@observer
-export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
+export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
@observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
_timeout: NodeJS.Timeout | undefined;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentWillUnmount() {
this._anchorDisposer?.();
}
- @action timeout = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25)));
+ @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25)));
componentDidMount() {
this._anchorDisposer = reaction(
() => [
- this.props.A.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
- this.props.B.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
+ this._props.A._props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
+ this._props.B._props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
],
action(() => {
this._start = Date.now();
@@ -53,9 +59,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
);
}
placeAnchors = () => {
- const { A, B, LinkDocs } = this.props;
+ const { A, B, LinkDocs } = this._props;
const linkDoc = LinkDocs[0];
- if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
+ if (SnappingManager.IsDragging || !A.ContentDiv || !B.ContentDiv) return;
setTimeout(
action(() => (this._opacity = 0.75)),
0
@@ -85,9 +91,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
}
} else {
const m = targetAhyperlink.getBoundingClientRect();
- const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- const mpx = mp[0] / A.props.PanelWidth();
- const mpy = mp[1] / A.props.PanelHeight();
+ const mp = A._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const mpx = mp[0] / A._props.PanelWidth();
+ const mpy = mp[1] / A._props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100;
if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
@@ -100,9 +106,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
}
} else {
const m = targetBhyperlink.getBoundingClientRect();
- const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- const mpx = mp[0] / B.props.PanelWidth();
- const mpy = mp[1] / B.props.PanelHeight();
+ const mp = B._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ const mpx = mp[0] / B._props.PanelWidth();
+ const mpy = mp[1] / B._props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100;
if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
@@ -115,18 +121,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this,
e,
(e, down, delta) => {
- this.props.LinkDocs[0].link_relationship_OffsetX = NumCast(this.props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
- this.props.LinkDocs[0].link_relationship_OffsetY = NumCast(this.props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
+ this._props.LinkDocs[0].link_relationship_OffsetX = NumCast(this._props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
+ this._props.LinkDocs[0].link_relationship_OffsetY = NumCast(this._props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
return false;
},
emptyFunction,
action(() => {
SelectionManager.DeselectAll();
- SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true);
- LinkManager.currentLink = this.props.LinkDocs[0];
+ SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
+ LinkManager.currentLink = this._props.LinkDocs[0];
this.toggleProperties();
// OverlayView.Instance.addElement(
- // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
+ // <LinkEditor sourceDoc={this._props.A._props.Document} linkDoc={this._props.LinkDocs[0]}
// showLinks={action(() => { })}
// />, { x: 300, y: 300 });
})
@@ -171,23 +177,23 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
@action
toggleProperties = () => {
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- SettingsManager.propertiesWidth = 250;
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
};
@action
onClickLine = () => {
SelectionManager.DeselectAll();
- SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true);
- LinkManager.currentLink = this.props.LinkDocs[0];
+ SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
+ LinkManager.currentLink = this._props.LinkDocs[0];
this.toggleProperties();
};
@computed.struct get renderData() {
this._start;
- SnappingManager.GetIsDragging();
- const { A, B, LinkDocs } = this.props;
+ SnappingManager.IsDragging;
+ const { A, B, LinkDocs } = this._props;
if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
@@ -200,8 +206,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const atop = this.visibleY(adiv);
const btop = this.visibleY(bdiv);
if (!a.width || !b.width) return undefined;
- const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
- const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+ const aDocBounds = (A._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+ const bDocBounds = (B._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
const aleft = this.visibleX(adiv);
const bleft = this.visibleX(bdiv);
const aclipped = aleft !== a.left || atop !== a.top;
@@ -223,12 +229,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
- const aActive = A.isSelected() || A.rootDoc[Brushed];
- const bActive = B.isSelected() || B.rootDoc[Brushed];
+ const aActive = A.SELECTED || A.Document[Brushed];
+ const bActive = B.SELECTED || B.Document[Brushed];
const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
- const link = this.props.LinkDocs[0];
+ const link = this._props.LinkDocs[0];
return {
a,
b,
@@ -250,7 +256,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
render() {
if (!this.renderData) return null;
- const link = this.props.LinkDocs[0];
+ const link = this._props.LinkDocs[0];
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 420e6a318..779a0bf96 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -5,7 +5,7 @@ import { DocumentManager } from '../../../util/DocumentManager';
import { LightboxView } from '../../LightboxView';
import './CollectionFreeFormLinksView.scss';
import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
-import React = require('react');
+import * as React from 'react';
@observer
export class CollectionFreeFormLinksView extends React.Component {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 856e195a3..f54726a00 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -4,11 +4,11 @@ import { Doc } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { PresBox } from '../../nodes/trails/PresBox';
import './CollectionFreeFormView.scss';
-import React = require('react');
+import * as React from 'react';
import { CollectionFreeFormView } from './CollectionFreeFormView';
export interface CollectionFreeFormPannableContentsProps {
- rootDoc: Doc;
+ Document: Doc;
viewDefDivClick?: ScriptField;
children?: React.ReactNode | undefined;
transition?: string;
@@ -20,7 +20,7 @@ export interface CollectionFreeFormPannableContentsProps {
@observer
export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
@computed get presPaths() {
- return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
+ return CollectionFreeFormView.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) =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
index 5fa01b102..7951aff65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -1,14 +1,12 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
.collectionFreeFormRemoteCursors-cont {
-
- position:absolute;
+ position: absolute;
z-index: $remoteCursors-zindex;
transform-origin: 'center center';
}
.collectionFreeFormRemoteCursors-canvas {
-
- position:absolute;
+ position: absolute;
width: 20px;
height: 20px;
opacity: 0.5;
@@ -21,4 +19,4 @@
// fontStyle: "italic",
margin-left: -12;
margin-top: 4;
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 9e8d92d7d..45e24bbb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -9,8 +9,8 @@ import { listSpec } from '../../../../fields/Schema';
import { Cast } from '../../../../fields/Types';
import { CollectionViewProps } from '../CollectionView';
import './CollectionFreeFormView.scss';
-import React = require('react');
-import v5 = require('uuid/v5');
+import * as React from 'react';
+import * as uuid from 'uuid';
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
@@ -42,7 +42,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
if (el) {
const ctx = el.getContext('2d');
if (ctx) {
- ctx.fillStyle = '#' + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + '22';
+ ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
ctx.fillRect(0, 0, 20, 20);
ctx.fillStyle = 'black';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 250760bd5..7d3acaea7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
.collectionfreeformview-none {
position: inherit;
@@ -255,3 +255,49 @@
background-color: rgba($color: #000000, $alpha: 0.4);
position: absolute;
}
+
+.collectionFreeform-infoUI {
+ position: absolute;
+ display: block;
+ top: 0;
+
+ color: white;
+ background-color: #5075ef;
+ border-radius: 5px;
+ box-shadow: 2px 2px 5px black;
+
+ margin: 15px;
+ padding: 10px;
+
+ .msg {
+ position: relative;
+ // display: block;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ }
+
+ .gif-container {
+ position: relative;
+ margin-top: 5px;
+ // display: block;
+
+ justify-content: center;
+ align-items: center;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+
+ .gif {
+ background-color: transparent;
+ height: 300px;
+ }
+ }
+
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d53049e04..1f4688729 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,8 +1,9 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, toJS } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
+import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { DocData, Height, Width } from '../../../../fields/DocSymbols';
@@ -45,13 +46,12 @@ import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import { TreeViewType } from '../CollectionTreeView';
import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
+import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
-import React = require('react');
-import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
export type collectionFreeformViewProps = {
NativeWidth?: () => number;
@@ -69,9 +69,13 @@ export type collectionFreeformViewProps = {
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
public get displayName() {
- return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')';
+ return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')';
} // this makes mobx trace() statements more descriptive
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable
public static ShowPresPaths = false;
@@ -90,19 +94,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer1: any;
public get isAnnotationOverlay() {
- return this.props.isAnnotationOverlay;
+ return this._props.isAnnotationOverlay;
}
public get scaleFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_scale';
+ return (this._props.viewField ?? '') + '_freeform_scale';
}
private get panXFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_panX';
+ return (this._props.viewField ?? '') + '_freeform_panX';
}
private get panYFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_panY';
+ return (this._props.viewField ?? '') + '_freeform_panY';
}
private get autoResetFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_autoReset';
+ return (this._props.viewField ?? '') + '_freeform_autoReset';
}
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -113,7 +117,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
- @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
+ @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region
@observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get contentViews() {
@@ -123,19 +127,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return renderableEles;
}
@computed get fitToContentVals() {
+ const hgt = this.contentBounds.b - this.contentBounds.y;
+ const wid = this.contentBounds.r - this.contentBounds.x;
return {
- bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
+ bounds: { ...this.contentBounds, cx: this.contentBounds.x + wid / 2, cy: this.contentBounds.y + hgt / 2 },
scale:
- !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
- ? 1
- : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+ (!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid)
+ ? 1 //
+ : Math.min(this._props.PanelHeight() / hgt, this._props.PanelWidth() / wid)) / (this._props.NativeDimScaling?.() || 1),
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
+ return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
- const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
+ const cb = Cast(this.dataDoc.contentBounds, listSpec('number'));
return cb
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: aggregateBounds(
@@ -145,29 +151,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
@computed get nativeWidth() {
- return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
+ return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
@computed get nativeHeight() {
- return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
+ return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
@computed get cachedCenteringShiftX(): number {
- const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const dv = this.props.DocumentView?.();
- const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ const dv = this._props.DocumentView?.();
+ const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
+ const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
- const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.layout_fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
- return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth();
+ return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get panZoomXf() {
return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
}
@computed get screenToLocalXf() {
- return this.props
+ return this._props
.ScreenToLocalTransform()
- .scale(this.props.isAnnotationOverlay ? 1 : 1)
+ .scale(this._props.isAnnotationOverlay ? 1 : 1)
.translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
.transform(this.panZoomXf);
}
@@ -182,15 +189,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const timecode = Math.round(time);
docs.forEach(doc => {
CollectionFreeFormDocumentView.animFields.forEach(val => {
- const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
+ const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
});
CollectionFreeFormDocumentView.animStringFields.forEach(val => {
- const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+ const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
});
CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
- const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+ const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
findexed?.length <= timecode + 1 && findexed.push(undefined as any);
});
});
@@ -216,46 +223,44 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _keyframeEditing = false;
@action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
getKeyFrameEditing = () => this._keyframeEditing;
- onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
- onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
- onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+ onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
fitContentOnce = () => {
- if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
this.layoutDoc._freeform_panY = vals.bounds.cy;
this.layoutDoc._freeform_scale = vals.scale;
};
freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
- ignoreNativeDimScaling = () => (this.fitContentsToBox ? true : false);
// freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[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], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () =>
- this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
ScreenToLocalXf = () => this.screenToLocalXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
- isAnyChildContentActive = () => this.props.isAnyChildContentActive();
- addLiveTextBox = (newBox: Doc) => {
- FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- this.addDocument(newBox);
+ isAnyChildContentActive = () => this._props.isAnyChildContentActive();
+ addLiveTextBox = (newDoc: Doc) => {
+ FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ this.addDocument(newDoc);
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if ((retVal = this.props.addDocument?.(newBox) || false)) {
+ if ((retVal = this._props.addDocument?.(newBox) || false)) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
} else {
- retVal = this.props.addDocument?.(newBox) || false;
+ retVal = this._props.addDocument?.(newBox) || false;
// bcz: deal with clusters
}
if (retVal) {
@@ -263,13 +268,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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}_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]));
}
}
- if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
}
}
@@ -284,15 +289,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
groupFocus = (anchor: Doc, options: DocFocusOptions) => {
- options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
- const res = this.props.focus(this.rootDoc, options);
+ options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
+ const res = this._props.focus(this.Document, options);
options.docTransform = undefined;
return res;
};
focus = (anchor: Doc, options: DocFocusOptions) => {
if (this._lightboxDoc) return;
- if (anchor === this.rootDoc) {
+ if (anchor === this.Document) {
if (options.willZoomCentered && options.zoomScale) {
this.fitContentOnce();
options.didMove = true;
@@ -301,7 +306,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
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.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+ const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
// focus on the document in the collection
@@ -323,11 +328,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
findDoc(dv => res(dv));
});
- @action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
- const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -342,7 +346,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate);
+ const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate);
if (this.Document._currentFrame !== undefined) {
CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -397,18 +401,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
dropDoc.y = yp - annoDragData.offset[1];
this.bringToFront(dropDoc);
}
- return dropDoc || this.rootDoc;
+ return dropDoc || this.Document;
};
return true;
}
@undoBatch
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
+ if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
const source =
- !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc
+ !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
: Docs.Create.FontIconDocument({
title: 'anchor',
@@ -425,7 +429,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
_xPadding: 0,
onClick: FollowLinkScript(),
});
- added = this.props.addDocument?.(source) ? true : false;
+ added = this._props.addDocument?.(source) ? true : false;
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
e.stopPropagation();
@@ -462,7 +466,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.childLayoutPairs
.map(pair => pair.layout)
.reduce((cluster, cd) => {
- const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
+ const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
if (grouping !== -1) {
const layoutDoc = Doc.Layout(cd);
const cx = NumCast(cd.x) - this._clusterDistance / 2;
@@ -479,11 +483,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (cluster !== -1) {
const ptsParent = e;
if (ptsParent) {
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+ const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
- de.moveDocument = this.props.moveDocument;
+ de.moveDocument = this._props.moveDocument;
de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
clusterDocs.map(v => v.ContentDiv!),
@@ -501,7 +505,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusters(_freeform_useClusters: boolean) {
- this.props.Document._freeform_useClusters = _freeform_useClusters;
+ this._props.Document._freeform_useClusters = _freeform_useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -509,7 +513,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusterDocs(docs: Doc[]) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._freeform_useClusters) {
+ if (this._props.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)));
const preferredInd = NumCast(docFirst.layout_cluster);
@@ -552,7 +556,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateCluster = (doc: Doc) => {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._freeform_useClusters) {
+ if (this._props.Document._freeform_useClusters) {
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
const preferredInd = NumCast(doc.layout_cluster);
doc.layout_cluster = -1;
@@ -582,35 +586,36 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
- let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
- switch (property) {
- 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)));
+ let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
+ if (doc && this.childDocList?.includes(doc))
+ switch (property) {
+ 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)));
+ }
}
- }
- break;
- case StyleProp.FillColor:
- if (doc && this.Document._currentFrame !== undefined) {
- return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
- }
- }
+ break;
+ case StyleProp.FillColor:
+ if (doc && this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+ }
+ }
return styleProp;
};
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
return true;
}
@@ -623,8 +628,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._downY = this._lastY = e.pageY;
this._downTime = Date.now();
const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
- if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
- if (!this.props.Document._isGroup) {
+ if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) {
+ if (!this.Document.isGroup) {
// group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
// prettier-ignore
switch (Doc.ActiveTool) {
@@ -636,7 +641,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
case InkTool.None:
- if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
+ if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
}
@@ -659,7 +664,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkWidth = ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale;
+ const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale;
const inkDoc = Docs.Create.InkDocument(
points,
{ title: ge.gesture.toString(),
@@ -698,7 +703,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
onEraserUp = (e: PointerEvent): void => {
- this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
+ this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.Document));
this._deleteList = [];
this._batch?.end();
};
@@ -708,7 +713,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this._lightboxDoc) this._lightboxDoc = undefined;
if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
if (this.onBrowseClickHandler()) {
- this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+ this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
e.stopPropagation();
e.preventDefault();
} else if (this.isContentActive() && e.shiftKey) {
@@ -731,9 +736,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const ctrlKey = e.ctrlKey && !e.shiftKey;
const shiftKey = e.shiftKey && !e.ctrlKey;
PresBox.Instance?.pauseAutoPres();
- this.props.DocumentView?.().clearViewTransition();
+ this._props.DocumentView?.().clearViewTransition();
const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate);
+ const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate);
this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
@@ -753,8 +758,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.stroke_width?.toString()) || '1');
- SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
+ SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
+ SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (!e.shiftKey) {
this._eraserLock++;
@@ -786,7 +791,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
// pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
- if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.zoomScaling()) {
+ if (!this._props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) {
this.pan(e);
e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
}
@@ -802,7 +807,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
.filter(
@@ -813,23 +818,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
eraserMax.X >= inkViewBounds.left &&
eraserMax.Y >= inkViewBounds.top
)
- .reduce((intersections, { inkStroke, inkView }) => {
- const { inkData } = inkStroke.inkScaledData();
- // Convert from screen space to ink space for the intersection.
- const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
- const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
- for (var i = 0; i < inkData.length - 3; i += 4) {
- const rawIntersects = InkField.Segment(inkData, i).intersects({
- // compute all unique intersections
- p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
- p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
- });
- const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type
- // return tuples of the inkingStroke intersected, and the t value of the intersection
- intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
- }
- return intersections;
- }, [] as { t: number; inkView: DocumentView }[]);
+ .reduce(
+ (intersections, { inkStroke, inkView }) => {
+ const { inkData } = inkStroke.inkScaledData();
+ // Convert from screen space to ink space for the intersection.
+ const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
+ const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
+ for (var i = 0; i < inkData.length - 3; i += 4) {
+ const rawIntersects = InkField.Segment(inkData, i).intersects({
+ // compute all unique intersections
+ p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+ p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
+ });
+ const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type
+ // return tuples of the inkingStroke intersected, and the t value of the intersection
+ intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
+ }
+ return intersections;
+ },
+ [] as { t: number; inkView: DocumentView }[]
+ );
};
/**
@@ -896,7 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs
.filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
.forEach(doc => {
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
+ const otherInk = DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())?.ComponentView as InkingStroke;
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));
@@ -928,7 +936,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
- if (this.Document._isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
+ if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
@@ -936,33 +944,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
- if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) {
+ if (deltaScale < 1 && invTransform.Scale <= NumCast(this.Document[this.scaleFieldKey + '_min'])) {
this.setPan(0, 0);
return;
}
- if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
- deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
+ if (deltaScale * invTransform.Scale > NumCast(this.Document[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
+ deltaScale = NumCast(this.Document[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
}
- if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
- deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
+ if (deltaScale * invTransform.Scale < NumCast(this.Document[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
+ deltaScale = NumCast(this.Document[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
}
const localTransform = invTransform.scaleAbout(deltaScale, x, y);
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
- this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
- this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+ this._props.Document[this.scaleFieldKey] = Math.abs(safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+ if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
PresBox.Instance?.pauseAutoPres();
- if (this.layoutDoc._Transform || this.props.Document.treeView_OutlineMode === TreeViewType.outline) return;
+ if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return;
e.stopPropagation();
- const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
- const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
+ const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
switch (
!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
@@ -970,7 +978,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
) {
case freeformScrollMode.Pan:
- if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) {
+ if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
@@ -978,8 +986,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
default:
case freeformScrollMode.Zoom:
- if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
- 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?
+ if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) {
+ 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();
}
break;
@@ -995,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
const measuredDocs = docs
- .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } }))
+ .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc._width), height: NumCast(doc._height) } }))
.filter(({ pos, size }) => pos && size)
.map(({ pos, size }) => ({ pos: pos!, size: size! }));
if (measuredDocs.length) {
@@ -1008,45 +1016,45 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
}),
{
- xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ xrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
}
);
- const scaling = this.zoomScaling() * (this.props.NativeDimScaling?.() || 1);
- const panelWidMax = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
- const panelWidMin = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 0 : 1);
- const panelHgtMax = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
- const panelHgtMin = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 0 : 1);
- if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
- else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
- if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
- else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
+ const scaling = this.zoomScaling() * (this._props.NativeDimScaling?.() || 1);
+ const panelWidMax = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelWidMin = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 0 : 1);
+ const panelHgtMax = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelHgtMin = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 0 : 1);
+ if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this._props.originTopLeft ? 0 : panelWidMax / 2);
+ else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this._props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+ if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this._props.originTopLeft ? 0 : panelHgtMax / 2);
+ else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this._props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
this.setPanZoomTransition(panTime);
- const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
+ const minScale = NumCast(this.dataDoc._freeform_scale_min, 1);
const scale = 1 - minScale / this.zoomScaling();
- const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
- const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
- const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
+ const minPanX = NumCast(this.dataDoc._freeform_panX_min, 0);
+ const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
+ const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
- const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
- const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
- const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+ const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale;
+ const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+ const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight();
const maxPanY =
minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
- scale * NumCast(this.rootDoc._panY_max, nativeHeight) +
- (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+ 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.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
- const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
- this.rootDoc.layout_scrollTop = undefined;
+ 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.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+ } 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.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+ setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10);
newPanY = minPanY;
}
!this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
@@ -1056,11 +1064,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
- if (collectionDoc?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) {
+ const collectionDoc = this._props.docViewPath().lastElement().Document;
+ if (collectionDoc?._type_collection !== CollectionViewType.Freeform) {
this.setPan(
- NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
- NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(),
nudgeTime,
true
);
@@ -1105,7 +1113,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
- if (this.Document._isGroup) return;
+ if (this.Document.isGroup) return;
this.setPanZoomTransition(transitionTime);
const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
@@ -1127,20 +1135,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newScale =
scale === 0
? NumCast(this.layoutDoc[this.scaleFieldKey])
- : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height)));
return {
- panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
- panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+ panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+ panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
scale: newScale,
};
}
- const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth();
- const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
+ const panelWidth = this._props.isAnnotationOverlay ? this.nativeWidth : this._props.PanelWidth();
+ const panelHeight = this._props.isAnnotationOverlay ? this.nativeHeight : this._props.PanelHeight();
const pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1);
const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1);
- const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
- const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+ const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this._props.isAnnotationOverlay ? pw / 2 : 0);
+ const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this._props.isAnnotationOverlay ? ph / 2 : 0);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1148,54 +1156,53 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return {
panX: (bounds.left + bounds.right) / 2,
panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
+ scale: Math.min(this._props.PanelHeight() / (bounds.bot - bounds.top), this._props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
};
}
return {
- panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
- panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+ panX: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ panY: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
};
};
- isContentActive = () => this.props.isContentActive();
+ isContentActive = () => this._props.isContentActive();
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
const below = !e.altKey && e.key !== 'Tab';
const layout_fieldKey = StrCast(docView.LayoutFieldKey);
- const newDoc = Doc.MakeCopy(docView.rootDoc, true);
- const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ const newDoc = Doc.MakeCopy(docView.Document, true);
+ const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)];
newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
- else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
- if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
+ if (below) newDoc.y = NumCast(docView.Document.y) + NumCast(docView.Document._height) + 10;
+ else newDoc.x = NumCast(docView.Document.x) + NumCast(docView.Document._width) + 10;
+ if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) {
+ newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
}
Doc.GetProto(newDoc).text = undefined;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
};
@computed get childPointerEvents() {
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
const pointerevents = DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents?.() ??
- (this.props.viewDefDivClick || //
- (engine === computePassLayout.name && !this.props.isSelected()) ||
+ : this._props.childPointerEvents?.() ??
+ (this._props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this._props.isSelected()) ||
this.isContentActive() === false
? 'none'
- : this.props.pointerEvents?.());
+ : this._props.pointerEvents?.());
return pointerevents;
}
- @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined;
+ @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
childPointerEventsFunc = () => this._childPointerEvents;
- childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
+ childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
@@ -1203,55 +1210,54 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<CollectionFreeFormDocumentViewWrapper
{...OmitKeys(entry, ['replica', 'pair']).omit}
key={childLayout[Id] + (entry.replica || '')}
- DataDoc={childData}
Document={childLayout}
+ TemplateDataDocument={childData}
dragStarting={this.dragStarting}
dragEnding={this.dragEnding}
- isGroupActive={this.props.isGroupActive}
- renderDepth={this.props.renderDepth + 1}
+ isGroupActive={this._props.isGroupActive}
+ renderDepth={this._props.renderDepth + 1}
hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
suppressSetHeight={this.layoutEngine ? true : false}
RenderCutoffProvider={this.renderCutoffProvider}
CollectionFreeFormView={this}
- LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
- LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+ LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
+ LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
- waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
+ waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
onClick={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
onBrowseClick={this.onBrowseClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf}
+ ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf}
PanelWidth={childLayout[Width]}
PanelHeight={childLayout[Height]}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
isContentActive={this.childContentsActive}
- focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
+ focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus}
addDocTab={this.addDocTab}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- moveDocument={this.props.moveDocument}
- pinToPres={this.props.pinToPres}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- docViewPath={this.props.docViewPath}
+ addDocument={this._props.addDocument}
+ removeDocument={this._props.removeDocument}
+ moveDocument={this._props.moveDocument}
+ pinToPres={this._props.pinToPres}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ docViewPath={this._props.docViewPath}
styleProvider={this.clusterStyleProvider}
- dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+ dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
bringToFront={this.bringToFront}
- layout_showTitle={this.props.childlayout_showTitle}
- dontRegisterView={this.props.dontRegisterView}
+ layout_showTitle={this._props.childlayout_showTitle}
+ dontRegisterView={this._props.dontRegisterView}
pointerEvents={this.childPointerEventsFunc}
- //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this
/>
);
}
addDocTab = action((doc: Doc, where: OpenWhere) => {
- if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where);
+ if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
switch (where) {
case OpenWhere.inParent:
- return this.props.addDocument?.(doc) || false;
+ return this._props.addDocument?.(doc) || false;
case OpenWhere.inParentFromScreen:
const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
return (
@@ -1263,7 +1269,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return doc;
})
) &&
- (!docContext || this.props.removeDocument?.(docContext))) ||
+ (!docContext || this._props.removeDocument?.(docContext))) ||
false
);
case undefined:
@@ -1277,9 +1283,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
}
- return this.props.addDocTab(doc, where);
+ return this._props.addDocTab(doc, where);
});
- @observable _lightboxDoc: Opt<Doc>;
+ @observable _lightboxDoc: Opt<Doc> = undefined;
getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
@@ -1289,9 +1295,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
const { z, zIndex } = childDoc;
const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
- const { x, y, _width, _height, opacity, _rotation } =
- layoutFrameNumber === undefined
- ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+ const { x, y, autoDim, _width, _height, opacity, _rotation } =
+ layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
+ ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
: CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
// prettier-ignore
const rotation = Cast(_rotation,'number',
@@ -1301,22 +1307,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
+ autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
transition: StrCast(childDocLayout.dataTransition),
- pair,
pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
+ pair,
replica: '',
};
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
- (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
+ (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload });
e.stopPropagation();
};
@@ -1371,7 +1378,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
poolData: Map<string, PoolData>,
engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
) {
- return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
+ return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
}
doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -1380,7 +1387,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@computed get layoutEngine() {
- return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
}
@computed get doInternalLayoutComputation() {
TraceMobx();
@@ -1414,28 +1421,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
+ const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
if (addAsAnnotation) {
- if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
};
+ infoUI = () => (this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />);
+
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
super.componentDidMount?.();
setTimeout(
action(() => {
this._firstRender = false;
this._disposers.groupBounds = reaction(
() => {
- if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) {
const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
}
@@ -1466,15 +1475,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.pointerevents = reaction(
() => {
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
return DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents?.() ??
- (this.props.viewDefDivClick || //
- (engine === computePassLayout.name && !this.props.isSelected()) ||
+ : this._props.childPointerEvents?.() ??
+ (this._props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this._props.isSelected()) ||
this.isContentActive() === false
? 'none'
- : this.props.pointerEvents?.());
+ : this._props.pointerEvents?.());
},
pointerevents => (this._childPointerEvents = pointerevents as any),
{ fireImmediately: true }
@@ -1482,7 +1491,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.active = reaction(
() => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
- active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
+ active => !SnappingManager.IsDragging && this.dataDoc[this.autoResetFieldKey] && active && this.resetView()
);
})
);
@@ -1529,11 +1538,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
updateIcon = () =>
CollectionFreeFormView.UpdateIcon(
this.layoutDoc[Id] + '-icon' + new Date().getTime(),
- this.props.docViewPath().lastElement().ContentDiv!,
+ this._props.docViewPath().lastElement().ContentDiv!,
NumCast(this.layoutDoc._width),
NumCast(this.layoutDoc._height),
- this.props.PanelWidth(),
- this.props.PanelHeight(),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
0,
1,
false,
@@ -1576,7 +1585,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
componentWillUnmount() {
- this.rootDoc[this.autoResetFieldKey] && this.resetView();
+ this.dataDoc[this.autoResetFieldKey] && this.resetView();
Object.values(this._disposers).forEach(disposer => disposer?.());
}
@@ -1593,7 +1602,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
+ this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
};
@undoBatch
@@ -1617,9 +1626,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
///
@undoBatch
resetView = () => {
- this.rootDoc[this.panXFieldKey] = NumCast(this.rootDoc[this.panXFieldKey + '_reset']);
- this.props.Document[this.panYFieldKey] = NumCast(this.rootDoc[this.panYFieldKey + '_reset']);
- this.rootDoc[this.scaleFieldKey] = NumCast(this.rootDoc[this.scaleFieldKey + '_reset'], 1);
+ this.layoutDoc[this.panXFieldKey] = NumCast(this.dataDoc[this.panXFieldKey + '_reset']);
+ this.layoutDoc[this.panYFieldKey] = NumCast(this.dataDoc[this.panYFieldKey + '_reset']);
+ this.layoutDoc[this.scaleFieldKey] = NumCast(this.dataDoc[this.scaleFieldKey + '_reset'], 1);
};
///
/// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS
@@ -1627,37 +1636,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
///
@undoBatch
toggleResetView = () => {
- this.rootDoc[this.autoResetFieldKey] = !this.rootDoc[this.autoResetFieldKey];
- if (this.rootDoc[this.autoResetFieldKey]) {
- this.rootDoc[this.panXFieldKey + '_reset'] = this.rootDoc[this.panXFieldKey];
- this.rootDoc[this.panYFieldKey + '_reset'] = this.rootDoc[this.panYFieldKey];
- this.rootDoc[this.scaleFieldKey + '_reset'] = this.rootDoc[this.scaleFieldKey];
+ this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey];
+ if (this.dataDoc[this.autoResetFieldKey]) {
+ this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey];
+ this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey];
+ this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey];
}
};
onContextMenu = (e: React.MouseEvent) => {
- if (this.props.isAnnotationOverlay || !ContextMenu.Instance) return;
+ if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
- !this.props.Document._isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
- !this.props.Document._isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
+ !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+ !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Toggle auto arrange',
event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
icon: 'compress-arrows-alt',
});
- if (this.props.setContentView === emptyFunction) {
+ if (this._props.setContentView === emptyFunction) {
!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' });
- appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
+ 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' });
+ this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
- this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+ this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
!Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
!Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -1665,11 +1674,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const options = ContextMenu.Instance.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- !this.props.isAnnotationOverlay &&
+ !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' });
- this.props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
+ 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' });
}
@@ -1680,10 +1689,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@undoBatch
- @action
transcribeStrokes = () => {
- if (this.props.Document._isGroup && this.props.Document.transcription) {
- const text = StrCast(this.props.Document.transcription);
+ if (this._props.Document.isGroup && this._props.Document.transcription) {
+ const text = StrCast(this._props.Document.transcription);
const lines = text.split('\n');
const height = 30 + 15 * lines.length;
@@ -1698,25 +1706,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => {
- if (visited.has(this.rootDoc)) return;
- visited.add(this.rootDoc);
- showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup));
+ if (visited.has(this.Document)) return;
+ visited.add(this.Document);
+ showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
const activeDocs = this.getActiveDocuments();
- const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+ const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
const 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.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))
+ .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))
.forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
const horizLines: number[] = [];
const vertLines: number[] = [];
const invXf = this.screenToLocalXf.inverse();
snappableDocs
- .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
+ .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
.forEach(doc => {
const { left, top, width, height } = docDims(doc);
const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1731,7 +1739,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
incrementalRender = action(() => {
- if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+ if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) {
const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1745,28 +1753,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// which will be a DocumentView. In this sitation, this freeform views acts as an annotation overlay for
// the underlying DocumentView and will pan and scoll with the underlying Documen tView.
@computed get underlayViews() {
- return this.props.children ? [this.props.children] : [];
+ return this._props.children ? [toJS(this._props.children)] : [];
}
@computed get placeholder() {
return (
- <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span>
+ <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
+ <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span>
</div>
);
}
brushedView = () => this._brushedView;
gridColor = () =>
- DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
+ DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
.fade(0.5)
.toString();
@computed get backgroundGrid() {
return (
<div>
<CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!?
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
panX={this.panX}
panY={this.panY}
color={this.gridColor}
@@ -1784,15 +1792,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.incrementalRender();
return (
<CollectionFreeFormPannableContents
- rootDoc={this.rootDoc}
+ Document={this.Document}
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.PanZoomCenterXf}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
- viewDefDivClick={this.props.viewDefDivClick}>
+ transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))}
+ viewDefDivClick={this._props.viewDefDivClick}>
{this.underlayViews}
{this.contentViews}
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ <CollectionFreeFormRemoteCursors {...this._props} key="remoteCursors" />
</CollectionFreeFormPannableContents>
);
}
@@ -1800,10 +1808,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
TraceMobx();
return (
<MarqueeView
- {...this.props}
+ {...this._props}
ref={this._marqueeViewRef}
- ungroup={this.rootDoc._isGroup ? this.promoteCollection : undefined}
- nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
+ ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
+ nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
slowLoadDocuments={this.slowLoadDocuments}
trySelectCluster={this.trySelectCluster}
@@ -1811,25 +1819,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
selectDocuments={this.selectDocuments}
addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.props.ScreenToLocalTransform}
+ getContainerTransform={this._props.ScreenToLocalTransform}
getTransform={this.ScreenToLocalXf}
panXFieldKey={this.panXFieldKey}
panYFieldKey={this.panYFieldKey}
isAnnotationOverlay={this.isAnnotationOverlay}>
{this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
{this.pannableContents}
- {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
+ {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null}
</MarqueeView>
);
}
@computed get nativeDimScaling() {
- if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
+ if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0;
const nw = this.nativeWidth;
const nh = this.nativeHeight;
- const hscale = nh ? this.props.PanelHeight() / nh : 1;
- const wscale = nw ? this.props.PanelWidth() / nw : 1;
- return wscale < hscale || this.layoutDoc.layout_fitWidth ? wscale : hscale;
+ const hscale = nh ? this._props.PanelHeight() / nh : 1;
+ const wscale = nw ? this._props.PanelWidth() / nw : 1;
+ return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
}
nativeDim = () => this.nativeDimScaling;
@@ -1846,13 +1854,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
transTime + 1
);
};
- lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
- lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
- lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+ lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15);
onPassiveWheel = (e: WheelEvent) => {
- const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
- const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
- this.props.isSelected() && !scrollable && e.preventDefault();
+ const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
+ this._props.isSelected() && !scrollable && e.preventDefault();
};
_oldWheel: any;
render() {
@@ -1875,18 +1883,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any),
+ pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : (this._props.pointerEvents?.() as any),
textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
- height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
+ height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
{this._lightboxDoc ? (
<div style={{ padding: 15, width: '100%', height: '100%' }}>
<DocumentView
- {...this.props}
+ {...this._props}
Document={this._lightboxDoc}
- DataDoc={undefined}
+ TemplateDataDocument={undefined}
PanelWidth={this.lightboxPanelWidth}
PanelHeight={this.lightboxPanelHeight}
NativeWidth={returnZero}
@@ -1898,8 +1906,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={this.props.childContentsActive ?? emptyFunction}
+ isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
+ isContentActive={this._props.childContentsActive ?? emptyFunction}
addDocTab={this.addDocTab}
ScreenToLocalTransform={this.lightboxScreenToLocal}
fitContentsToBox={undefined}
@@ -1909,7 +1917,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
) : (
<>
{this._firstRender ? this.placeholder : this.marqueeView}
- {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+ {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
{!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
</>
)}
@@ -1929,16 +1937,16 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
const browseTransitionTime = 500;
SelectionManager.DeselectAll();
dv &&
- DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+ DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
if (!focused) {
- const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
let containers = dv.props.docViewPath();
let parFfview = dv.CollectionFreeFormView;
for (var cont of containers) {
parFfview = parFfview ?? cont.CollectionFreeFormView;
}
- while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ while (parFfview?.Document.isGroup) parFfview = parFfview.props.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.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
Doc.linkFollowHighlight(dv?.props.Document, false);
}
@@ -1946,68 +1954,31 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
}
ScriptingGlobals.add(CollectionBrowseClick);
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
- !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
+ !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
});
ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
- !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
+ !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
});
ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
- const selView = SelectionManager.Views();
+ const selView = SelectionManager.Views;
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
});
ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
- SelectionManager.Views().forEach(view =>
- view.props.pinToPres(view.rootDoc, {
- currentFrame: Cast(view.rootDoc.currentFrame, 'number', null),
+ SelectionManager.Views.forEach(view =>
+ view.props.pinToPres(view.Document, {
+ currentFrame: Cast(view.Document.currentFrame, 'number', null),
pinData: {
poslayoutview: pinContent,
dataview: pinContent,
},
- pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()),
+ pinViewport: MarqueeView.CurViewBounds(view.Document, view.props.PanelWidth(), view.props.PanelHeight()),
})
);
});
ScriptingGlobals.add(function bringToFront() {
- SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc));
+ SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document));
});
ScriptingGlobals.add(function sendToBack(doc: Doc) {
- SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true));
+ SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
});
-ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
- SelectionManager.Views().forEach(view => {
- 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");
- if (!keys) return;
-
- const children = DocListCast(view.rootDoc[Doc.LayoutFieldKey(view.rootDoc)]);
- let csvRows = [];
- csvRows.push(keys.join(','));
- for (let i=0; i < children.length; i++) {
- let 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, '');
- eachRow.push(cell);
- }
- csvRows.push(eachRow);
- }
- const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
- const options = { x:0, y:-300, title: 'schemaTable', _width: 300, _height: 100, type: 'text/csv' };
- const file = new File([blob], 'schemaTable', options);
- const loading = Docs.Create.LoadingDocument(file, options);
- loading.presentation_openInLightbox = true;
- DocUtils.uploadFileToDoc(file, {}, loading);
-
- if (view.ComponentView?.addDocument) {
- // loading.dataViz_fromSchema = true;
- loading.dataViz_asSchema = view.layoutDoc;
- SchemaCSVPopUp.Instance.setView(view);
- SchemaCSVPopUp.Instance.setTarget(view.layoutDoc);
- SchemaCSVPopUp.Instance.setDataVizDoc(loading);
- SchemaCSVPopUp.Instance.setVisible(true);
- }
- })
-}); \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 607f9fb95..79cc534dc 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -1,14 +1,11 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { IconButton } from 'browndash-components';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { unimplementedFunction } from '../../../../Utils';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-import { IconButton } from 'browndash-components';
-import { StrCast } from '../../../../fields/Types';
-import { Doc } from '../../../../fields/Doc';
-import { computed } from 'mobx';
import { SettingsManager } from '../../../util/SettingsManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -21,9 +18,9 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
public hideMarquee: () => void = unimplementedFunction;
public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public isShown = () => this._opacity > 0;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
-
+ makeObservable(this);
MarqueeOptionsMenu.Instance = this;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2092ecb7a..2c65726b2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,5 +1,7 @@
-import { action, computed, observable } from 'mobx';
+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 { Doc, Opt } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -7,25 +9,24 @@ import { InkData, InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField, nullAudio } from '../../../../fields/URLField';
+import { ImageField } from '../../../../fields/URLField';
import { GetEffectiveAcl } from '../../../../fields/util';
-import { intersectRect, lightOrDark, returnFalse, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
-import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
import { SelectionManager } from '../../../util/SelectionManager';
+import { freeformScrollMode } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { PreviewCursor } from '../../PreviewCursor';
import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
-import { PreviewCursor } from '../../PreviewCursor';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { SubCollectionViewProps } from '../CollectionSubView';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
-import React = require('react');
-import { freeformScrollMode } from '../../../util/SettingsManager';
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -51,12 +52,17 @@ export interface MarqueeViewBounds {
}
@observer
-export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> {
+export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
const ps = NumCast(pinDoc._freeform_scale, 1);
return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
}
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
private _commandExecuted = false;
@observable _lastX: number = 0;
@observable _lastY: number = 0;
@@ -67,7 +73,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@observable _lassoFreehand: boolean = false;
@computed get Transform() {
- return this.props.getTransform();
+ return this._props.getTransform();
}
@computed get Bounds() {
// nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
@@ -79,7 +85,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
componentDidMount() {
- this.props.setPreviewCursor?.(this.setPreviewCursor);
+ this._props.setPreviewCursor?.(this.setPreviewCursor);
}
@action
@@ -101,20 +107,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+ cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
- } else if (e.key === 'u' && this.props.ungroup) {
+ } else if (e.key === 'u' && this._props.ungroup) {
e.stopPropagation();
- this.props.ungroup();
+ this._props.ungroup();
} else if (e.key === ':') {
- DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
+ DocUtils.addDocumentCreatorMenuItems(this._props.addLiveTextDocument, this._props.addDocument || returnFalse, x, y);
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
- this.props.selectDocuments(this.props.activeDocuments());
+ this._props.selectDocuments(this._props.activeDocuments());
e.stopPropagation();
} else if (e.key === 'q' && e.ctrlKey) {
e.preventDefault();
@@ -136,7 +142,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
ns.map(line => {
const indent = line.search(/\S|$/);
const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
- this.props.addDocument?.(newBox);
+ this._props.addDocument?.(newBox);
ypos += 40 * this.Transform.Scale;
});
})();
@@ -147,8 +153,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
- this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
+ Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
);
@@ -159,7 +165,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
slide.y = y;
FormattedTextBox.SelectOnLoad = slide[Id];
TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
- this.props.addDocument?.(slide);
+ this._props.addDocument?.(slide);
e.stopPropagation();
}*/ else if (e.key === 'p' && e.ctrlKey) {
e.preventDefault();
@@ -169,10 +175,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.pasteTable(ns, x, y);
})();
e.stopPropagation();
- } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
- FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
+ } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
+ FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
- this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
+ this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
e.stopPropagation();
}
};
@@ -203,7 +209,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const file = new File([blob], 'droppedTable', options);
const loading = Docs.Create.LoadingDocument(file, options);
DocUtils.uploadFileToDoc(file, {}, loading);
- this.props.addDocument?.(loading);
+ this._props.addDocument?.(loading);
}
@action
@@ -214,9 +220,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
// allow marquee if right drag/meta drag, or pan mode
if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) {
- this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
+ this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document);
e.preventDefault();
- } else PreviewCursor.Visible = false;
+ } else PreviewCursor.Instance.Visible = false;
};
@action
@@ -243,10 +249,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (this._visible) {
const mselect = this.marqueeSelect();
if (!e.shiftKey) {
- SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
+ SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document);
}
- const docs = mselect.length ? mselect : [this.props.Document];
- this.props.selectDocuments(docs);
+ const docs = mselect.length ? mselect : [this._props.Document];
+ this._props.selectDocuments(docs);
}
const hideMarquee = () => {
this.hideMarquee();
@@ -285,14 +291,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = this._lastX = x;
this._downY = this._lastY = y;
this._commandExecuted = false;
- PreviewCursor.Visible = false;
- PreviewCursor.Doc = undefined;
+ PreviewCursor.Instance.Visible = false;
+ PreviewCursor.Instance.Doc = undefined;
} else if (drag) {
this._downX = this._lastX = x;
this._downY = this._lastY = y;
this._commandExecuted = false;
- PreviewCursor.Visible = false;
- PreviewCursor.Doc = undefined;
+ PreviewCursor.Instance.Visible = false;
+ PreviewCursor.Instance.Doc = undefined;
this.cleanupInteractions(true);
document.addEventListener('pointermove', this.onPointerMove, true);
document.addEventListener('pointerup', this.onPointerUp, true);
@@ -300,10 +306,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
+ const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
- PreviewCursor.Doc = doc;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments);
+ PreviewCursor.Instance.Doc = doc;
+ PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
}
this.clearSelection();
}
@@ -311,11 +317,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
- if (this.props.pointerEvents?.() === 'none') return;
+ if (this._props.pointerEvents?.() === 'none') return;
if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
if (Doc.ActiveTool === InkTool.None) {
- if (!this.props.trySelectCluster(e.shiftKey)) {
- !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+ if (!this._props.trySelectCluster(e.shiftKey)) {
+ !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
} else e.stopPropagation();
}
// let the DocumentView stopPropagation of this event when it selects this document
@@ -332,22 +338,22 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
hideMarquee = () => (this._visible = false);
@undoBatch
- @action
- delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
+ 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);
this.hideMarquee();
- };
+ });
getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
const newCollection = creator
? creator(selected, { title: 'nested stack' })
: ((doc: Doc) => {
Doc.GetProto(doc).data = new List<Doc>(selected);
+ Doc.GetProto(doc).isGroup = makeGroup;
Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
doc._freeform_panX = doc._freeform_panY = 0;
return doc;
@@ -355,7 +361,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
newCollection.isSystem = undefined;
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
- newCollection._isGroup = makeGroup;
newCollection._dragWhenActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
@@ -366,17 +371,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
@undoBatch
- @action
- pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(d => this.props.removeDocument?.(d));
+ selected.forEach(d => this._props.removeDocument?.(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
- this.props.addDocument?.(newCollection);
- this.props.selectDocuments([newCollection]);
+ this._props.addDocument?.(newCollection);
+ this._props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
/**
* This triggers the TabDocView.PinDoc method which is the universal method
@@ -385,35 +389,32 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
* This one is unique in that it includes the bounds associated with marquee view.
*/
@undoBatch
- @action
- pinWithView = () => {
- this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds });
+ pinWithView = action(() => {
+ this._props.pinToPres(this._props.Document, { pinViewport: this.Bounds });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
@undoBatch
- @action
- collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+ collection = action((e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
const selected = selection ?? this.marqueeSelect(false);
const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
- this.props.removeDocument?.(selected);
+ this._props.removeDocument?.(selected);
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
newCollection._currentFrame = activeFrame;
- this.props.addDocument?.(newCollection);
- this.props.selectDocuments([newCollection]);
+ this._props.addDocument?.(newCollection);
+ this._props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
@undoBatch
- @action
- syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
if (e instanceof KeyboardEvent ? e.key === 'i' : true) {
const inks = selected.filter(s => s.type === DocumentType.INK);
@@ -472,15 +473,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// }
const lines = results.filter((r: any) => r.category === 'line');
const text = lines.map((l: any) => l.recognizedText).join('\r\n');
- this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
+ this._props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
});
}
- };
+ });
@undoBatch
summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false).map(d => {
- this.props.removeDocument?.(d);
+ this._props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
d.y = NumCast(d.y) - this.Bounds.top;
return d;
@@ -500,8 +501,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
- this.props.addDocument?.(portal);
- this.props.addLiveTextDocument(summary);
+ this._props.addDocument?.(portal);
+ this._props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
});
@@ -582,17 +583,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
(this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
}
};
- this.props
+ this._props
.activeDocuments()
.filter(doc => !doc.z && !doc._lockedPosition)
.map(selectFunc);
if (!selection.length && selectBackgrounds)
- this.props
+ this._props
.activeDocuments()
.filter(doc => doc.z === undefined)
.map(selectFunc);
if (!selection.length)
- this.props
+ this._props
.activeDocuments()
.filter(doc => doc.z !== undefined)
.map(selectFunc);
@@ -601,8 +602,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@computed get marqueeDiv() {
const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY];
- const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
- const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ const p = this._props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
+ const v = this._lassoFreehand ? [0, 0] : this._props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
return (
<div
className="marquee"
@@ -610,8 +611,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
transform: `translate(${p[0]}px, ${p[1]}px)`,
width: Math.abs(v[0]),
height: Math.abs(v[1]),
- color: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
- borderColor: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
+ color: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
+ borderColor: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
zIndex: 2000,
}}>
{' '}
@@ -620,7 +621,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
<polyline //
points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')}
fill="none"
- stroke={lightOrDark(this.props.Document?.backgroundColor ?? 'white')}
+ stroke={lightOrDark(this._props.Document?.backgroundColor ?? 'white')}
strokeWidth="1"
strokeDasharray="3"
/>
@@ -635,19 +636,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
- if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
+ if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
(e as any).handlePan = true;
const bounds = this.MarqueeRef?.getBoundingClientRect();
- if (!this.props.Document._freeform_noAutoPan && !this.props.renderDepth && bounds) {
+ if (!this._props.Document._freeform_noAutoPan && !this._props.renderDepth && bounds) {
const dragX = e.detail.clientX;
const dragY = e.detail.clientY;
const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
if (deltaX !== 0 || deltaY !== 0) {
- this.props.Document[this.props.panYFieldKey] = NumCast(this.props.Document[this.props.panYFieldKey]) + deltaY / 2;
- this.props.Document[this.props.panXFieldKey] = NumCast(this.props.Document[this.props.panXFieldKey]) + deltaX / 2;
+ this._props.Document[this._props.panYFieldKey] = NumCast(this._props.Document[this._props.panYFieldKey]) + deltaY / 2;
+ this._props.Document[this._props.panXFieldKey] = NumCast(this._props.Document[this._props.panXFieldKey]) + deltaX / 2;
}
}
e.stopPropagation();
@@ -661,7 +662,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.MarqueeRef = r;
}}
style={{
- overflow: StrCast(this.props.Document._overflow),
+ overflow: StrCast(this._props.Document._overflow),
cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
}}
onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 274012000..1e19964d7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, Lambda, observable, reaction } from 'mobx';
+import { action, computed, Lambda, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
@@ -27,6 +27,11 @@ export class CollectionGridView extends CollectionSubView() {
@observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
private dropLocation: object = {}; // sets the drop location for external drops
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
@computed get numCols() {
@@ -174,7 +179,7 @@ export class CollectionGridView extends CollectionSubView() {
}
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined);
+ isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
/**
*
* @param layout
@@ -191,7 +196,7 @@ export class CollectionGridView extends CollectionSubView() {
NativeHeight={returnZero}
setContentView={emptyFunction}
Document={layout}
- DataDoc={layout.resolvedDataDoc as Doc}
+ TemplateDataDocument={layout.resolvedDataDoc as Doc}
isContentActive={this.isChildContentActive}
PanelWidth={width}
PanelHeight={height}
@@ -199,7 +204,7 @@ export class CollectionGridView extends CollectionSubView() {
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
onClick={this.onChildClickHandler}
renderDepth={this.props.renderDepth + 1}
- dontCenter={this.props.Document.centerY ? undefined : 'y'}
+ dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
/>
);
}
@@ -277,7 +282,6 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Handles internal drop of Dash documents.
*/
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const savedLayouts = this.savedLayoutList;
const dropped = de.complete.docDragData?.droppedDocuments;
@@ -292,7 +296,6 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Handles external drop of images/PDFs etc from outside Dash.
*/
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
this.dropLocation = this.screenToCell(e.clientX, e.clientY);
super.onExternalDrop(e, {});
@@ -350,7 +353,7 @@ export class CollectionGridView extends CollectionSubView() {
undoBatch(
action(() => {
const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
- FormattedTextBox.SelectOnLoad = text[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ 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.props.Document, this.props.fieldKey, text);
this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
})
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index d0c14a21d..b8ceec139 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
@import '../../_nodeModuleOverrides';
.collectionLinearView {
@@ -18,9 +18,9 @@
user-select: none;
}
- > input:not(:checked) ~ &.true {
- background-color: transparent;
- }
+ // > input:not(:checked) ~ &.true {
+ // background-color: transparent;
+ // }
.collectionLinearView {
display: flex;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 4267a9059..f1fb68003 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,24 +1,24 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+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 { Doc, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
+import { UndoStack } from '../../UndoStack';
import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { UndoStack } from '../../UndoStack';
import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionLinearView.scss';
@@ -33,12 +33,15 @@ import './CollectionLinearView.scss';
*/
@observer
export class CollectionLinearView extends CollectionSubView() {
- @observable public addMenuToggle = React.createRef<HTMLInputElement>();
- @observable private _selectedIndex = -1;
private _dropDisposer?: DragManager.DragDropDisposer;
private _widthDisposer?: IReactionDisposer;
private _selectedDisposer?: IReactionDisposer;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentWillUnmount() {
this._dropDisposer?.();
this._widthDisposer?.();
@@ -48,7 +51,7 @@ export class CollectionLinearView extends CollectionSubView() {
componentDidMount() {
this._widthDisposer = reaction(
- () => 5 + NumCast(this.rootDoc.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),
+ () => 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),
{ fireImmediately: true }
);
@@ -58,7 +61,7 @@ export class CollectionLinearView extends CollectionSubView() {
if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
};
- dimension = () => NumCast(this.rootDoc._height);
+ dimension = () => NumCast(this.layoutDoc._height);
getTransform = (ele: Opt<HTMLDivElement>) => {
if (!ele) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
@@ -126,8 +129,8 @@ export class CollectionLinearView extends CollectionSubView() {
Currently playing:
{CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
<>
- <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}>
- {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')}
+ <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}>
+ {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')}
</span>
<FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '}
<FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '}
@@ -170,28 +173,28 @@ export class CollectionLinearView extends CollectionSubView() {
}}>
<DocumentView
Document={doc}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ addDocTab={this._props.addDocTab}
pinToPres={emptyFunction}
- dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
+ dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
+ rootSelected={this.rootSelected}
+ removeDocument={this._props.removeDocument}
ScreenToLocalTransform={docXf}
PanelWidth={doc[Width]}
PanelHeight={nested || doc._height ? doc[Height] : this.dimension}
- renderDepth={this.props.renderDepth + 1}
- dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
+ renderDepth={this._props.renderDepth + 1}
+ dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
focus={emptyFunction}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
hideResizeHandles={true}
/>
</div>
@@ -205,8 +208,8 @@ export class CollectionLinearView extends CollectionSubView() {
const menuOpener = (
<Toggle
- text={Cast(this.props.Document.icon, 'string', null)}
- icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
+ text={Cast(this.Document.icon, 'string', null)}
+ icon={Cast(this.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
color={SettingsManager.userColor}
background={SettingsManager.userVariantColor}
type={Type.TERT}
@@ -215,7 +218,7 @@ export class CollectionLinearView extends CollectionSubView() {
toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
onClick={() => {
this.layoutDoc.linearView_IsOpen = !isExpanded;
- ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.rootDoc }, console.log);
+ ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
}}
tooltip={isExpanded ? 'Close' : 'Open'}
fillWidth={true}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index cb0d5e03f..f983fd815 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -1,6 +1,6 @@
.collectionMulticolumnView_contents {
display: flex;
- overflow: hidden;
+ //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template)
width: 100%;
height: 100%;
@@ -17,7 +17,7 @@
}
.contentFittingDocumentView {
- width: unset;
+ margin: auto;
}
.label-wrapper {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index c2586fb4b..e12bcd8b0 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Button } from 'browndash-components';
-import { action, computed } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
@@ -37,6 +37,11 @@ const resizerWidth = 8;
@observer
export class CollectionMulticolumnView extends CollectionSubView() {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
/**
* @returns the list of layout documents whose width unit is
* *, denoting that it will be displayed with a ratio, not fixed pixel, value
@@ -122,7 +127,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
private get totalRatioAllocation(): number | undefined {
const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
- return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin);
+ return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._xMargin);
}
}
@@ -184,7 +189,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
let offset = 0;
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0);
+ return this._props.ScreenToLocalTransform().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
}
offset += this.lookupPixels(candidate) + resizerWidth;
}
@@ -192,7 +197,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
};
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
@@ -219,8 +223,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
if (this.childDocs.includes(d)) {
if (dropInd > this.childDocs.indexOf(d)) dropInd--;
}
- Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
- Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d);
+ Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1);
}
})
);
@@ -233,51 +237,58 @@ export class CollectionMulticolumnView extends CollectionSubView() {
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
- isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
isChildContentActive = () => {
- const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
- return this.props.isContentActive?.() === false || childDocsActive === false
+ const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
+ return this._props.isContentActive?.() === false || childDocsActive === false
? false //
- : this.props.isDocumentActive?.() && childDocsActive
- ? true
- : undefined;
+ : this._props.isDocumentActive?.() && childDocsActive
+ ? true
+ : undefined;
};
- getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
+ getDisplayDoc = (childLayout: Doc) => {
+ const width = () => this.lookupPixels(childLayout);
+ const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+ const dxf = () =>
+ 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={layout}
- DataDoc={layout.resolvedDataDoc as Doc}
- styleProvider={this.props.styleProvider}
- docViewPath={this.props.docViewPath}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- renderDepth={this.props.renderDepth + 1}
+ Document={childLayout}
+ TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
+ styleProvider={this._props.styleProvider}
+ docViewPath={this._props.docViewPath}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ renderDepth={this._props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
- shouldNotScale={shouldNotScale}
rootSelected={this.rootSelected}
- dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
+ dragAction={(this._props.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
suppressSetHeight={true}
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
- isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- fitContentsToBox={this.props.fitContentsToBox}
- focus={this.props.focus}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ fitContentsToBox={this._props.fitContentsToBox}
+ focus={this._props.focus}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- dontRegisterView={this.props.dontRegisterView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
+ dontRegisterView={this._props.dontRegisterView}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
bringToFront={returnFalse}
+ dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
/>
);
};
@@ -288,34 +299,23 @@ export class CollectionMulticolumnView extends CollectionSubView() {
@computed
private get contents(): JSX.Element[] | null {
const { childLayoutPairs } = this;
- const { Document, PanelHeight } = this.props;
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
- const aspect = Doc.NativeAspect(layout, undefined, true);
- const width = () => this.lookupPixels(layout);
- const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
- const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(height() * aspect, width()));
- const docheight = () => Math.min(docwidth() / aspect, height());
- const dxf = () =>
- this.lookupIndividualTransform(layout)
- .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin))
- .scale(this.props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
collector.push(
- <Tooltip title={'Tab: ' + StrCast(layout.title)}>
- <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
- {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
- <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} />
- <WidthLabel layout={layout} collectionDoc={Document} />
+ <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
+ <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
+ {this.getDisplayDoc(layout)}
+ <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} />
+ <WidthLabel layout={layout} collectionDoc={this.Document} />
</div>
</Tooltip>,
<ResizeBar
width={resizerWidth}
key={'resizer' + i}
- styleProvider={this.props.styleProvider}
- isContentActive={this.props.isContentActive}
- select={this.props.select}
+ styleProvider={this._props.styleProvider}
+ isContentActive={this._props.isContentActive}
+ select={this._props.select}
columnUnitLength={this.getColumnUnitLength}
toLeft={layout}
toRight={childLayoutPairs[i + 1]?.layout}
@@ -326,18 +326,18 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return collector;
}
- render(): JSX.Element {
+ render() {
return (
<div
- className={'collectionMulticolumnView_contents'}
+ className="collectionMulticolumnView_contents"
ref={this.createDashEventsTarget}
style={{
- width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
- height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
- marginLeft: NumCast(this.props.Document._xMargin),
- marginRight: NumCast(this.props.Document._xMargin),
- marginTop: NumCast(this.props.Document._yMargin),
- marginBottom: NumCast(this.props.Document._yMargin),
+ width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`,
+ height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`,
+ marginLeft: NumCast(this.Document._xMargin),
+ marginRight: NumCast(this.Document._xMargin),
+ marginTop: NumCast(this.Document._yMargin),
+ marginBottom: NumCast(this.Document._yMargin),
}}>
{this.contents}
</div>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index ec7200a03..f44eacb2a 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -1,6 +1,6 @@
.collectionMultirowView_contents {
display: flex;
- overflow: hidden;
+ //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template)
width: 100%;
height: 100%;
flex-direction: column;
@@ -10,11 +10,6 @@
flex-direction: row;
height: 100%;
align-items: center;
- margin: auto;
-
- .contentFittingDocumentView {
- height: unset;
- }
.label-wrapper {
display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 249c3551b..7dbc18e60 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
@@ -33,6 +33,11 @@ const resizerHeight = 8;
@observer
export class CollectionMultirowView extends CollectionSubView() {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
/**
* @returns the list of layout documents whose width unit is
* *, denoting that it will be displayed with a ratio, not fixed pixel, value
@@ -118,7 +123,7 @@ export class CollectionMultirowView extends CollectionSubView() {
private get totalRatioAllocation(): number | undefined {
const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
- return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin);
+ return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin);
}
}
@@ -180,7 +185,7 @@ export class CollectionMultirowView extends CollectionSubView() {
let offset = 0;
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1));
+ return this._props.ScreenToLocalTransform().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
}
offset += this.lookupPixels(candidate) + resizerHeight;
}
@@ -188,7 +193,6 @@ export class CollectionMultirowView extends CollectionSubView() {
};
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
@@ -215,8 +219,8 @@ export class CollectionMultirowView extends CollectionSubView() {
if (this.childDocs.includes(d)) {
if (dropInd > this.childDocs.indexOf(d)) dropInd--;
}
- Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
- Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d);
+ Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1);
}
})
);
@@ -229,51 +233,58 @@ export class CollectionMultirowView extends CollectionSubView() {
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
- isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
isChildContentActive = () => {
- const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
- return this.props.isContentActive?.() === false || childDocsActive === false
+ const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
+ return this._props.isContentActive?.() === false || childDocsActive === false
? false //
- : this.props.isDocumentActive?.() && childDocsActive
- ? true
- : undefined;
+ : this._props.isDocumentActive?.() && childDocsActive
+ ? true
+ : undefined;
};
- getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
+ getDisplayDoc = (layout: Doc) => {
+ const height = () => this.lookupPixels(layout);
+ const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+ const dxf = () =>
+ 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}
- DataDoc={layout.resolvedDataDoc as Doc}
- styleProvider={this.props.styleProvider}
- docViewPath={this.props.docViewPath}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- renderDepth={this.props.renderDepth + 1}
+ TemplateDataDocument={layout.resolvedDataDoc as Doc}
+ styleProvider={this._props.styleProvider}
+ docViewPath={this._props.docViewPath}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ renderDepth={this._props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
- shouldNotScale={shouldNotScale}
rootSelected={this.rootSelected}
- dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType}
+ dropAction={StrCast(this.Document.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
- isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- fitContentsToBox={this.props.fitContentsToBox}
- dragAction={this.props.childDragAction}
- focus={this.props.focus}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ fitContentsToBox={this._props.fitContentsToBox}
+ dragAction={this._props.childDragAction}
+ focus={this._props.focus}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- dontRegisterView={this.props.dontRegisterView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
+ dontRegisterView={this._props.dontRegisterView}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
bringToFront={returnFalse}
+ dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
/>
);
};
@@ -284,29 +295,18 @@ export class CollectionMultirowView extends CollectionSubView() {
@computed
private get contents(): JSX.Element[] | null {
const { childLayoutPairs } = this;
- const { Document, PanelWidth } = this.props;
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
- const aspect = Doc.NativeAspect(layout, undefined, true);
- const height = () => this.lookupPixels(layout);
- const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
- const docheight = () => Math.min(width() / aspect, height());
- const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(width(), docheight() * aspect));
- const dxf = () =>
- this.lookupIndividualTransform(layout)
- .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin) - (height() - docheight()) / 2)
- .scale(this.props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
collector.push(
- <div className="document-wrapper" style={{ height: height() }} key={'wrapper' + i}>
- {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
- <HeightLabel layout={layout} collectionDoc={Document} />
+ <div className="document-wrapper" style={{ flexDirection: 'row', height: this.lookupPixels(layout) }} key={'wrapper' + i}>
+ {this.getDisplayDoc(layout)}
+ <HeightLabel layout={layout} collectionDoc={this.Document} />
</div>,
<ResizeBar
height={resizerHeight}
- styleProvider={this.props.styleProvider}
- isContentActive={this.props.isContentActive}
+ styleProvider={this._props.styleProvider}
+ isContentActive={this._props.isContentActive}
key={'resizer' + i}
columnUnitLength={this.getRowUnitLength}
toTop={layout}
@@ -318,17 +318,17 @@ export class CollectionMultirowView extends CollectionSubView() {
return collector;
}
- render(): JSX.Element {
+ render() {
return (
<div
- className={'collectionMultirowView_contents'}
+ className="collectionMultirowView_contents"
style={{
- width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
- height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
- marginLeft: NumCast(this.props.Document._xMargin),
- marginRight: NumCast(this.props.Document._xMargin),
- marginTop: NumCast(this.props.Document._yMargin),
- marginBottom: NumCast(this.props.Document._yMargin),
+ width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`,
+ height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`,
+ marginLeft: NumCast(this.Document._xMargin),
+ marginRight: NumCast(this.Document._xMargin),
+ marginTop: NumCast(this.Document._yMargin),
+ marginBottom: NumCast(this.Document._yMargin),
}}
ref={this.createDashEventsTarget}>
{this.contents}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 76bd392a5..02131ae22 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.scss';
+@import '../../global/globalCssVariables.module.scss';
.collectionSchemaView {
cursor: default;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 190e4ff2a..2546f5b02 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,7 +1,7 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, observe } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
@@ -18,6 +18,7 @@ import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
@@ -57,6 +58,11 @@ export class CollectionSchemaView extends CollectionSubView() {
private _tableContentRef: HTMLDivElement | null = null;
private _menuTarget = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
static _rowHeight: number = 50;
static _rowSingleLineHeight: number = 32;
public static _minColWidth: number = 25;
@@ -68,16 +74,16 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _menuKeys: string[] = [];
@observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
@observable _colEles: HTMLDivElement[] = [];
- @observable _displayColumnWidths: number[] | undefined;
- @observable _columnMenuIndex: number | undefined;
+ @observable _displayColumnWidths: number[] | undefined = undefined;
+ @observable _columnMenuIndex: number | undefined = undefined;
@observable _newFieldWarning: string = '';
@observable _makeNewField: boolean = false;
@observable _newFieldDefault: any = 0;
@observable _newFieldType: ColumnType = ColumnType.Number;
@observable _menuValue: string = '';
- @observable _filterColumnIndex: number | undefined;
+ @observable _filterColumnIndex: number | undefined = undefined;
@observable _filterSearchValue: string = '';
- @observable _selectedCell: [Doc, number] | undefined;
+ @observable _selectedCell: [Doc, number] | undefined = undefined;
// target HTMLelement portal for showing a popup menu to edit cell values.
public get MenuTarget() {
@@ -85,12 +91,12 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get _selectedDocs() {
- const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+ const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
if (!selected.length) {
- for (const sel of SelectionManager.Docs()) {
+ for (const sel of SelectionManager.Docs) {
const contextPath = DocumentManager.GetContextPath(sel, true);
- if (contextPath.includes(this.rootDoc)) {
- const parentInd = contextPath.indexOf(this.rootDoc);
+ if (contextPath.includes(this.Document)) {
+ const parentInd = contextPath.indexOf(this.Document);
return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
}
}
@@ -107,7 +113,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get tableWidth() {
- return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
+ return this._props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
}
@computed get columnKeys() {
@@ -139,14 +145,13 @@ export class CollectionSchemaView extends CollectionSubView() {
return BoolCast(this.layoutDoc.sortDesc);
}
- @action
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
document.addEventListener('keydown', this.onKeyDown);
Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
this._keysDisposer = observe(
- this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+ this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
change => {
switch (change.type as any) {
case 'splice':
@@ -154,7 +159,7 @@ export class CollectionSchemaView extends CollectionSubView() {
(change as any).added.forEach((doc: Doc) => // for each document added
Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
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(''))))));
+ !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, 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
}
@@ -239,7 +244,6 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
setColumnSort = (field: string | undefined, desc: boolean = false) => {
this.layoutDoc.sortField = field;
this.layoutDoc.sortDesc = desc;
@@ -248,7 +252,6 @@ export class CollectionSchemaView extends CollectionSubView() {
addRow = (doc: Doc | Doc[]) => this.addDocument(doc);
@undoBatch
- @action
changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
if (!this.documentKeys.includes(newKey)) {
this.addNewKey(newKey, defaultVal);
@@ -260,7 +263,6 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
addColumn = (key: string, defaultVal?: any) => {
if (!this.documentKeys.includes(key)) {
this.addNewKey(key, defaultVal);
@@ -281,7 +283,6 @@ export class CollectionSchemaView extends CollectionSubView() {
addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
@undoBatch
- @action
removeColumn = (index: number) => {
if (this.columnKeys.length === 1) return;
const currWidths = this.storedColumnWidths.slice();
@@ -320,8 +321,8 @@ export class CollectionSchemaView extends CollectionSubView() {
change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
}
- this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale;
- this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale;
+ this._displayColumnWidths[shrinking] -= change * this._props.ScreenToLocalTransform().Scale;
+ this._displayColumnWidths[growing] += change * this._props.ScreenToLocalTransform().Scale;
return false;
}
@@ -335,7 +336,6 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
moveColumn = (fromIndex: number, toIndex: number) => {
let currKeys = this.columnKeys.slice();
currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
@@ -379,7 +379,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
highlightDropColumn = (e: PointerEvent) => {
e.stopPropagation();
- const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ const mouseX = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
const index = this.findDropIndex(mouseX);
this._colEles.forEach((colRef, i) => {
let leftStyle = '';
@@ -416,8 +416,8 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
clearSelection = () => SelectionManager.DeselectAll();
- selectRows = (rootDoc: Doc, lastSelected: Doc) => {
- const index = this.rowIndex(rootDoc);
+ selectRows = (doc: Doc, lastSelected: Doc) => {
+ const index = this.rowIndex(doc);
const lastSelectedRow = this.rowIndex(lastSelected);
const startRow = Math.min(lastSelectedRow, index);
const endRow = Math.max(lastSelectedRow, index);
@@ -437,10 +437,9 @@ export class CollectionSchemaView extends CollectionSubView() {
setDropIndex = (index: number) => (this._closestDropIndex = index);
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
- const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+ const mouseX = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
const index = this.findDropIndex(mouseX);
this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
@@ -473,7 +472,6 @@ export class CollectionSchemaView extends CollectionSubView() {
return false;
};
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
};
@@ -485,7 +483,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
- const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+ const movedWidth = this._props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
this.layoutDoc.schema_previewWidth = width;
return false;
@@ -509,8 +507,8 @@ export class CollectionSchemaView extends CollectionSubView() {
const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const rect = found.getBoundingClientRect();
- const localRect = this.props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height);
- if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this.props.PanelHeight()) {
+ const localRect = this._props.ScreenToLocalTransform().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;
smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
return focusSpeed;
@@ -757,7 +755,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get renderFilterOptions() {
const keyOptions: string[] = [];
const columnKey = this.columnKeys[this._filterColumnIndex!];
- const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+ const allDocs = DocListCast(this.dataDoc[this._props.fieldKey]);
allDocs.forEach(doc => {
const value = StrCast(doc[columnKey]);
if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) {
@@ -781,9 +779,9 @@ export class CollectionSchemaView extends CollectionSubView() {
onClick={e => e.stopPropagation()}
onChange={action(e => {
if (e.target.checked) {
- Doc.setDocFilter(this.props.Document, columnKey, key, 'check');
+ Doc.setDocFilter(this.Document, columnKey, key, 'check');
} else {
- Doc.setDocFilter(this.props.Document, columnKey, key, 'remove');
+ Doc.setDocFilter(this.Document, columnKey, key, 'remove');
}
})}
checked={bool}
@@ -830,8 +828,8 @@ export class CollectionSchemaView extends CollectionSubView() {
}
rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight);
sortedDocsFunc = () => this.sortedDocs;
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive();
+ screenToLocal = () => this._props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
previewWidthFunc = () => this.previewWidth;
render() {
return (
@@ -840,7 +838,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div
className="schema-table"
style={{ width: `calc(100% - ${this.previewWidth}px)` }}
- onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+ onWheel={e => this._props.isContentActive() && e.stopPropagation()}
ref={r => {
// prevent wheel events from passively propagating up through containers
r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
@@ -866,7 +864,7 @@ export class CollectionSchemaView extends CollectionSubView() {
openContextMenu={this.openContextMenu}
dragColumn={this.dragColumn}
setColRef={this.setColRef}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
/>
))}
</div>
@@ -892,16 +890,15 @@ export class CollectionSchemaView extends CollectionSubView() {
{Array.from(this._selectedDocs).lastElement() && (
<DocumentView
Document={Array.from(this._selectedDocs).lastElement()}
- DataDoc={undefined}
fitContentsToBox={returnTrue}
dontCenter={'y'}
onClickScriptDisable="always"
focus={emptyFunction}
defaultDoubleClick={returnIgnore}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
rootSelected={this.rootSelected}
PanelWidth={this.previewWidthFunc}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
isContentActive={returnTrue}
isDocumentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocal}
@@ -910,12 +907,12 @@ export class CollectionSchemaView extends CollectionSubView() {
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
docViewPath={returnEmptyDoclist}
- moveDocument={this.props.moveDocument}
+ moveDocument={this._props.moveDocument}
addDocument={this.addRow}
- removeDocument={this.props.removeDocument}
+ removeDocument={this._props.removeDocument}
whenChildContentsActiveChanged={returnFalse}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
bringToFront={returnFalse}
/>
)}
@@ -939,7 +936,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
return (
<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 className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
+ <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} />
</div>
))}
@@ -956,9 +953,14 @@ interface CollectionSchemaViewDocProps {
}
@observer
-class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocProps> {
- tableWidthFunc = () => this.props.schema.tableWidth;
- screenToLocalXf = () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - this.props.index * this.props.rowHeight());
+class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaViewDocProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ tableWidthFunc = () => this._props.schema.tableWidth;
+ screenToLocalXf = () => this._props.schema._props.ScreenToLocalTransform().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
if (property === StyleProp.Opacity) return 1;
return DefaultStyleProvider(doc, props, property);
@@ -966,31 +968,30 @@ class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocPro
render() {
return (
<DocumentView
- key={this.props.doc[Id]}
- {...this.props.schema.props}
- LayoutTemplate={this.props.schema.props.childLayoutTemplate}
- LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey, this.props.index)}
- Document={this.props.doc}
- DataDoc={undefined}
- renderDepth={this.props.schema.props.renderDepth + 1}
+ key={this._props.doc[Id]}
+ {...this._props.schema._props}
+ LayoutTemplate={this._props.schema._props.childLayoutTemplate}
+ LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
+ Document={this._props.doc}
+ renderDepth={this._props.schema._props.renderDepth + 1}
PanelWidth={this.tableWidthFunc}
- PanelHeight={this.props.rowHeight}
+ PanelHeight={this._props.rowHeight}
styleProvider={this.noOpacityStyleProvider}
waitForDoubleClickToClick={returnNever}
defaultDoubleClick={returnIgnore}
dragAction="move"
onClickScriptDisable="always"
- focus={this.props.schema.focusDocument}
- childFilters={this.props.schema.childDocFilters}
- childFiltersByRanges={this.props.schema.childDocRangeFilters}
- searchFilterDocs={this.props.schema.searchFilterDocs}
- rootSelected={this.props.schema.rootSelected}
+ focus={this._props.schema.focusDocument}
+ childFilters={this._props.schema.childDocFilters}
+ childFiltersByRanges={this._props.schema.childDocRangeFilters}
+ searchFilterDocs={this._props.schema.searchFilterDocs}
+ rootSelected={this._props.schema.rootSelected}
ScreenToLocalTransform={this.screenToLocalXf}
bringToFront={emptyFunction}
dragWhenActive={true}
- isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
+ isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
isContentActive={emptyFunction}
- whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
+ whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
hideDecorations={true}
hideTitle={true}
hideDocumentButtonBar={true}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 65e47f441..04443b4a7 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
@@ -26,7 +26,7 @@ export interface SchemaColumnHeaderProps {
export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
@observable _ref: HTMLDivElement | null = null;
- @computed get fieldKey() {
+ get fieldKey() {
return this.props.columnKeys[this.props.columnIndex];
}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 7346c4f12..5a3be826b 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -1,13 +1,13 @@
-import React = require('react');
import { IconButton, Size } from 'browndash-components';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
+import * as React from 'react';
import { CgClose } from 'react-icons/cg';
import { FaExternalLinkAlt } from 'react-icons/fa';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { Doc } from '../../../../fields/Doc';
import { BoolCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
@@ -28,38 +28,42 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
public static LayoutString(fieldKey: string, rowIndex: number) {
return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
}
-
private _ref: HTMLDivElement | null = null;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
bounds = () => this._ref?.getBoundingClientRect();
@computed get schemaView() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+ return this._props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
}
@computed get schemaDoc() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+ return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
}
@computed get rowIndex() {
- return this.schemaView?.rowIndex(this.rootDoc) ?? -1;
+ return this.schemaView?.rowIndex(this.Document) ?? -1;
}
componentDidMount(): void {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
select = (ctrlKey: boolean, shiftKey: boolean) => {
if (!this.schemaView) return;
const lastSelected = Array.from(this.schemaView._selectedDocs).lastElement();
- if (shiftKey && lastSelected) this.schemaView.selectRows(this.rootDoc, lastSelected);
+ if (shiftKey && lastSelected) this.schemaView.selectRows(this.Document, lastSelected);
else {
- this.props.select?.(ctrlKey);
+ this._props.select?.(ctrlKey);
}
};
onPointerEnter = (e: any) => {
- if (SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+ if (SnappingManager.IsDragging && this._props.isContentActive()) {
document.removeEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointermove', this.onPointerMove);
}
@@ -103,18 +107,18 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
return (
<div
className="schema-row"
- style={{ height: this.props.PanelHeight(), backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined }}
+ style={{ height: this._props.PanelHeight(), backgroundColor: this._props.isSelected() ? Colors.LIGHT_BLUE : undefined }}
onPointerEnter={this.onPointerEnter}
onPointerLeave={this.onPointerLeave}
ref={(row: HTMLDivElement | null) => {
- row && this.schemaView?.addRowRef?.(this.rootDoc, row);
+ row && this.schemaView?.addRowRef?.(this.Document, row);
this._ref = row;
}}>
<div
className="row-menu"
style={{
width: CollectionSchemaView._rowMenuWidth,
- pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
+ pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
}}>
<IconButton
tooltip="close"
@@ -128,7 +132,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
emptyFunction,
undoable(e => {
e.stopPropagation();
- this.props.removeDocument?.(this.rootDoc);
+ this._props.removeDocument?.(this.Document);
}, 'Delete Row')
)
}
@@ -145,7 +149,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
emptyFunction,
undoable(e => {
e.stopPropagation();
- this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
+ this._props.addDocTab(this.Document, OpenWhere.addRight);
}, 'Open schema Doc preview')
)
}
@@ -155,13 +159,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
{this.schemaView?.columnKeys?.map((key, index) => (
<SchemaTableCell
key={key}
- Document={this.rootDoc}
+ Document={this.Document}
col={index}
fieldKey={key}
allowCRs={false} // to enter text with new lines, must use \n
columnWidth={this.columnWidth(index)}
rowHeight={this.schemaView.rowHeightFunc}
- isRowActive={this.props.isContentActive}
+ isRowActive={this._props.isContentActive}
getFinfo={this.getFinfo}
selectCell={this.selectCell}
deselectCell={this.deselectCell}
@@ -172,7 +176,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
transform={() => {
const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index;
const x = this.schemaView?.displayColumnWidths.reduce((p, c, i) => (i <= ind ? p + c : p), 0);
- const y = (this.props.rowIndex ?? 0) * this.props.PanelHeight();
+ const y = (this._props.rowIndex ?? 0) * this._props.PanelHeight();
return new Transform(x + CollectionSchemaView._rowMenuWidth, y, 1);
}}
/>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 9d5b533d1..85269028b 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,27 +1,28 @@
-import * as React from 'react';
-import Select, { MenuPlacement } from 'react-select';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
+import * as React from 'react';
import DatePicker from 'react-datepicker';
+import Select from 'react-select';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
import { FInfo } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch, undoable } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DefaultStyleProvider } from '../../StyleProvider';
import { Colors } from '../../global/globalEnums';
import { OpenWhere } from '../../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../nodes/FieldView';
-import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { FieldViewProps } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
export interface SchemaTableCellProps {
@@ -47,7 +48,12 @@ export interface SchemaTableCellProps {
}
@observer
-export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
static addFieldDoc = (doc: Doc, where: OpenWhere) => {
DocFocusOrOpen(doc);
return true;
@@ -72,7 +78,6 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
searchFilterDocs: returnEmptyDoclist,
styleProvider: DefaultStyleProvider,
docViewPath: returnEmptyDoclist,
- rootSelected: returnFalse,
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
@@ -97,12 +102,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
}
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
@computed get defaultCellContent() {
- const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props);
+ const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<div
@@ -111,19 +116,21 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
color,
textDecoration,
width: '100%',
+ pointerEvents,
}}>
<EditableView
- oneLine={this.props.oneLine}
- allowCRs={this.props.allowCRs}
- contents={<FieldView {...fieldProps} />}
+ oneLine={this._props.oneLine}
+ allowCRs={this._props.allowCRs}
+ contents={undefined}
+ fieldContents={fieldProps}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
- this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
}
- const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
- this.props.finishEdit?.();
+ const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
/>
@@ -132,8 +139,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
}
get getCellType() {
- const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
- const cellValue = this.props.Document[this.props.fieldKey];
+ const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType;
+ const cellValue = this._props.Document[this._props.fieldKey];
if (cellValue instanceof ImageField) return ColumnType.Image;
if (cellValue instanceof DateField) return ColumnType.Date;
if (cellValue instanceof RichTextField) return ColumnType.RTF;
@@ -152,11 +159,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
const cellType: ColumnType = this.getCellType;
// prettier-ignore
switch (cellType) {
- case ColumnType.Image: return <SchemaImageCell {...this.props} />;
- case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
- case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
- case ColumnType.Enumeration: return <SchemaEnumerationCell {...this.props} options={this.props.getFinfo(this.props.fieldKey)?.values?.map(val => val.toString())} />;
- case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
+ case ColumnType.Image: return <SchemaImageCell {...this._props} />;
+ case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />;
+ case ColumnType.RTF: return <SchemaRTFCell {...this._props} />;
+ case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />;
+ case ColumnType.Date: // return <SchemaDateCell {...this._props} />;
default: return this.defaultCellContent;
}
}
@@ -165,8 +172,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
return (
<div
className="schema-table-cell"
- onPointerDown={action(e => !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 }}>
+ onPointerDown={action(e => !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>
);
@@ -175,8 +182,13 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
// mj: most of this is adapted from old schema code so I'm not sure what it does tbh
@observer
-export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
- @observable _previewRef: HTMLImageElement | undefined;
+export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @observable _previewRef: HTMLImageElement | undefined = undefined;
choosePath(url: URL) {
if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
@@ -188,8 +200,8 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
}
get url() {
- const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
const altpaths = alts
.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
.filter(url => url)
@@ -226,10 +238,10 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
};
render() {
- const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
- // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+ const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio
+ // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px
// const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
- const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
+ 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} />;
@@ -237,22 +249,26 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
}
@observer
-export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
- @observable _pickingDate: boolean = false;
+export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+ @observable _pickingDate: boolean = false;
@computed get date(): DateField {
// if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
- return DateCast(this.props.Document[this.props.fieldKey]);
+ return DateCast(this._props.Document[this._props.fieldKey]);
}
@action
handleChange = (date: any) => {
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
- // this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+ // this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
- this.props.Document[this.props.fieldKey] = new DateField(date as Date);
+ this._props.Document[this._props.fieldKey] = new DateField(date as Date);
//}
};
@@ -261,53 +277,64 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
}
}
@observer
-export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
+export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
selectedFunc = () => this.selected;
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
}
@observer
-export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
+export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ 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, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
<input
style={{ marginRight: 4 }}
type="checkbox"
- checked={BoolCast(this.props.Document[this.props.fieldKey])}
+ 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());
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
}
- KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
})}
/>
<EditableView
- contents={<FieldView {...fieldProps} />}
+ contents={undefined}
+ fieldContents={fieldProps}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
- this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
}
- const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
- this.props.finishEdit?.();
+ const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
return set;
})}
/>
@@ -316,14 +343,19 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
}
}
@observer
-export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> {
+export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableCellProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ 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 options = this.props.options?.map(facet => ({ value: facet, label: facet }));
+ const { color, textDecoration, fieldProps, 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 }}>
<div style={{ width: '100%' }}>
@@ -357,17 +389,17 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps>
...base,
left: 0,
top: 0,
- transform: `translate(${this.props.transform().TranslateX}px, ${this.props.transform().TranslateY}px)`,
- width: Number(base.width) * this.props.transform().Scale,
+ transform: `translate(${this._props.transform().TranslateX}px, ${this._props.transform().TranslateY}px)`,
+ width: Number(base.width) * this._props.transform().Scale,
zIndex: 9999,
}),
}}
- menuPortalTarget={this.props.menuTarget}
+ menuPortalTarget={this._props.menuTarget}
menuPosition={'absolute'}
- placeholder={StrCast(this.props.Document[this.props.fieldKey], 'select...')}
+ placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
options={options}
isMulti={false}
- onChange={val => KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+ onChange={val => KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
/>
</div>
</div>
diff --git a/src/client/views/collections/goldenLayoutTheme.css b/src/client/views/collections/goldenLayoutTheme.css
new file mode 100644
index 000000000..cf577d6b1
--- /dev/null
+++ b/src/client/views/collections/goldenLayoutTheme.css
@@ -0,0 +1,132 @@
+.lm_goldenlayout {
+ background: #000000;
+}
+.lm_content {
+ background: #222222;
+}
+.lm_dragProxy .lm_content {
+ box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
+}
+.lm_dropTargetIndicator {
+ box-shadow: inset 0 0 30px #000000;
+ outline: 1px dashed #cccccc;
+ transition: all 200ms ease;
+}
+.lm_dropTargetIndicator .lm_inner {
+ background: #000000;
+ opacity: 0.2;
+}
+.lm_splitter {
+ background: #000000;
+ opacity: 0.001;
+ transition: opacity 200ms ease;
+}
+.lm_splitter:hover,
+.lm_splitter.lm_dragging {
+ background: #444444;
+ opacity: 1;
+}
+.lm_header {
+ height: 20px;
+ user-select: none;
+}
+.lm_header.lm_selectable {
+ cursor: pointer;
+}
+.lm_header .lm_tab {
+ font-family: Arial, sans-serif;
+ font-size: 12px;
+ color: #999999;
+ background: #111111;
+ box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.3);
+ margin-right: 2px;
+ padding-bottom: 2px;
+ padding-top: 2px;
+}
+.lm_header .lm_tab .lm_close_tab {
+ width: 11px;
+ height: 11px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAATElEQVR4nG3OwQ0DMQwDwZGRBtR/j1YJzMc5+IDoR+yCVO29g+pu981MFgqZmRdAfU7+CYWcbF11LwALjpBL0N0qybNx/RPU+gOeiS/+XCRwDlTgkQAAAABJRU5ErkJggg==);
+ background-position: center center;
+ background-repeat: no-repeat;
+ top: 4px;
+ right: 6px;
+ opacity: 0.4;
+}
+.lm_header .lm_tab .lm_close_tab:hover {
+ opacity: 1;
+}
+.lm_header .lm_tab.lm_active {
+ border-bottom: none;
+ box-shadow: 0 -2px 2px #000000;
+ padding-bottom: 3px;
+}
+.lm_header .lm_tab.lm_active .lm_close_tab {
+ opacity: 1;
+}
+.lm_dragProxy.lm_bottom .lm_header .lm_tab,
+.lm_stack.lm_bottom .lm_header .lm_tab {
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3);
+}
+.lm_dragProxy.lm_bottom .lm_header .lm_tab.lm_active,
+.lm_stack.lm_bottom .lm_header .lm_tab.lm_active {
+ box-shadow: 0 2px 2px #000000;
+}
+.lm_selected .lm_header {
+ background-color: #452500;
+}
+.lm_tab:hover,
+.lm_tab.lm_active {
+ background: #222222;
+ color: #dddddd;
+}
+.lm_header .lm_controls .lm_tabdropdown:before {
+ color: #ffffff;
+}
+.lm_controls > li {
+ position: relative;
+ background-position: center center;
+ background-repeat: no-repeat;
+ opacity: 0.4;
+ transition: opacity 300ms ease;
+}
+.lm_controls > li:hover {
+ opacity: 1;
+}
+.lm_controls .lm_popout {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAPklEQVR4nI2Q0QoAIAwCNfr/X7aXCpGN8snBdgejJOzckpkxs9jR6K6T5JpU0nWl5pSXTk7qwh8SnNT+CAAWCgkKFpuSWsUAAAAASUVORK5CYII=);
+}
+.lm_controls .lm_maximise {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==);
+}
+.lm_controls .lm_close {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQUlEQVR4nHXOQQ4AMAgCQeT/f6aXpsGK3jSTuCVJAAr7iBdoAwCKd0nwfaAdHbYERw5b44+E8JoBjEYGMBq5gAYP3usUDu2IvoUAAAAASUVORK5CYII=);
+}
+.lm_maximised .lm_header {
+ background-color: #000000;
+}
+.lm_maximised .lm_controls .lm_maximise {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVR4nGP8//8/AzGAiShVI1YhCwMDA8OsWbPwBmZaWhoj0SYCAN1lBxMAX4n0AAAAAElFTkSuQmCC);
+}
+.lm_transition_indicator {
+ background-color: #000000;
+ border: 1px dashed #555555;
+}
+.lm_popin {
+ cursor: pointer;
+}
+.lm_popin .lm_bg {
+ background: #ffffff;
+ opacity: 0.3;
+}
+.lm_popin .lm_icon {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAWklEQVR4nJWOyw3AIAxDHcQC7L8jbwT3AlJBfNp3SiI7dtRaLSlKKeoA1oEsKSQZCEluexw8Tm3ohk+E7bnOUHUGcNh+HwbBygw4AZ7FN/Lt84p0l+yTflV8AKQyLdcCRJi/AAAAAElFTkSuQmCC);
+ background-position: center center;
+ background-repeat: no-repeat;
+ border-left: 1px solid #eeeeee;
+ border-top: 1px solid #eeeeee;
+ opacity: 0.7;
+}
+.lm_popin:hover .lm_icon {
+ opacity: 1;
+} /*# sourceMappingURL=goldenlayout-dark-theme.css.map */
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.module.scss
index 44e8efe23..44e8efe23 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.module.scss
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.module.scss.d.ts
index bcbb1f068..bcbb1f068 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.module.scss.d.ts
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3af523d73..814003955 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -14,25 +14,25 @@ import { undoable, UndoManager } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { GestureOverlay } from '../GestureOverlay';
import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
-import { InkTranscription } from '../InkTranscription';
+// import { InkTranscription } from '../InkTranscription';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { WebBox } from '../nodes/WebBox';
ScriptingGlobals.add(function IsNoneSelected() {
- return SelectionManager.Views().length <= 0;
+ return SelectionManager.Views.length <= 0;
}, 'are no document selected');
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setView(view: string) {
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Docs.lastElement();
selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selectedViews = SelectionManager.Views();
+ const selectedViews = SelectionManager.Views;
if (Doc.ActiveTool !== InkTool.None) {
if (checkResult) {
return ActiveFillColor();
@@ -41,26 +41,26 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
} else if (selectedViews.length) {
if (checkResult) {
const selView = selectedViews.lastElement();
- const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
- const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
- return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
+ const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
}
selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
selectedViews.forEach(dv => {
- const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
- const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
if (contentFrameNumber !== undefined) {
const obj: { [key: string]: Opt<string> } = {};
obj[fieldKey] = color;
- CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, obj);
+ CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
} else {
- dv.rootDoc['_' + fieldKey] = color;
+ dv.Document['_' + fieldKey] = color;
}
});
} else {
- const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : [];
+ const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.currentLink ? [LinkManager.currentLink] : [];
if (checkResult) {
return selected.lastElement()?._backgroundColor ?? 'transparent';
}
@@ -72,10 +72,10 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
if (checkResult) {
- return SelectionManager.Views().length ? StrCast(SelectionManager.Docs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
+ return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
}
- if (SelectionManager.Views().length) {
- SelectionManager.Docs().forEach(doc => {
+ if (SelectionManager.Views.length) {
+ SelectionManager.Docs.forEach(doc => {
Doc.GetProto(doc).layout_headingColor = color;
doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
});
@@ -88,7 +88,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
- const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined;
if (checkResult) {
if (NumCast(selected?.Document.z) >= 1) return true;
return false;
@@ -97,7 +97,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
});
ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
['grid', {
@@ -140,13 +140,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
return map.get(attr)?.checkResult(selected);
}
const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
- SelectionManager.Views().map(dv => map.get(attr)?.setDoc(dv.rootDoc, dv));
+ SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
setTimeout(() => batch.end(), 100);
});
ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
['font', {
@@ -189,13 +189,13 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
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' ? SelectionManager.Docs().some(doc => doc.layout_centered):
+ [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance.textVcenter:
(RichTextMenu.Instance.textAlign === where)):
- where === 'vcent' ? BoolCast(Doc.UserDoc().layout_centered):
+ where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
(Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? (where === 'vcent' ? SelectionManager.Docs().forEach(doc => doc.layout_centered = !doc.layout_centered):
+ toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
- where === 'vcent' ? Doc.UserDoc().layout_centered = !Doc.UserDoc().layout_centered:
+ where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
(Doc.UserDoc().textAlign = where))}]); // prettier-ignore
// prettier-ignore
const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
@@ -280,7 +280,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
return d;
})
);
- ffView.props.removeDocument?.(selected);
+ ffView._props.removeDocument?.(selected);
// TODO: nda - this is the code to actually get a new grouped collection
const newCollection = marqViewRef?.getCollection(selected, undefined, true);
if (newCollection) {
@@ -289,18 +289,18 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
}
// nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
- newCollection && ffView.props.addDocument?.(newCollection);
+ newCollection && ffView._props.addDocument?.(newCollection);
// TODO: nda - will probably need to go through and only remove the unprocessed selected docs
ffView.unprocessedDocs = [];
- InkTranscription.Instance.transcribeInk(newCollection, selected, false);
+ // InkTranscription.Instance.transcribeInk(newCollection, selected, false);
});
}
CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
}
function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
- InkTranscription.Instance?.createInkGroup();
+ // 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)
@@ -338,7 +338,7 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement() ?? Doc.UserDoc();
+ const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
// prettier-ignore
const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
@@ -367,33 +367,31 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
return map.get(option)?.checkResult();
}
map.get(option)?.setMode();
- SelectionManager.Docs()
- .filter(doc => doc.type === DocumentType.INK)
- .map(doc => map.get(option)?.setInk(doc));
+ SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc));
});
/** WEB
* webSetURL
**/
ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement();
- if (selected?.rootDoc.type === DocumentType.WEB) {
+ const selected = SelectionManager.Views.lastElement();
+ if (selected?.Document.type === DocumentType.WEB) {
if (checkResult) {
- return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
+ return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
}
selected.ComponentView?.setData?.(url);
- //selected.rootDoc.data = new WebField(url);
+ //selected.Document.data = new WebField(url);
}
});
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
+ const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
if (checkResult) {
return selected?.forward(checkResult) ? undefined : 'lightGray';
}
selected?.forward();
});
ScriptingGlobals.add(function webBack(checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
+ const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
if (checkResult) {
return selected?.back(checkResult) ? undefined : 'lightGray';
}
@@ -404,7 +402,7 @@ ScriptingGlobals.add(function webBack(checkResult?: boolean) {
* toggleSchemaPreview
**/
ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Docs.lastElement();
if (checkResult && selected) {
const result: boolean = NumCast(selected.schema_previewWidth) > 0;
if (result) return Colors.MEDIUM_BLUE;
@@ -418,7 +416,7 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
}
});
ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Docs.lastElement();
if (checkResult && selected) {
return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
}
@@ -431,7 +429,7 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
* groupBy
*/
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => (doc._text_fontFamily = key));
+ SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
const editorView = RichTextMenu.Instance.TextView?.EditorView;
if (checkResult) {
return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 0b9f32eee..636b6415c 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.linkMenu {
width: auto;
@@ -11,7 +11,9 @@
display: inline-block;
position: relative;
border: 1px solid #e4e4e4;
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+ box-shadow:
+ 0 10px 20px rgba(0, 0, 0, 0.19),
+ 0 6px 6px rgba(0, 0, 0, 0.23);
max-height: 230px;
overflow-y: scroll;
z-index: 10;
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 9dc133e28..b7376e901 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,13 +1,14 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc } from '../../../fields/Doc';
import { LinkManager } from '../../util/LinkManager';
+import { SettingsManager } from '../../util/SettingsManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentView } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
import './LinkMenu.scss';
import { LinkMenuGroup } from './LinkMenuGroup';
-import React = require('react');
-import { SettingsManager } from '../../util/SettingsManager';
interface Props {
docView: DocumentView;
@@ -20,9 +21,13 @@ interface Props {
* the outermost component for the link menu of a node that contains a list of its linked nodes
*/
@observer
-export class LinkMenu extends React.Component<Props> {
+export class LinkMenu extends ObservableReactComponent<Props> {
_editorRef = React.createRef<HTMLDivElement>();
@observable _linkMenuRef = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
clear = () => this.props.clearLinkEditor?.();
@@ -34,7 +39,7 @@ export class LinkMenu extends React.Component<Props> {
}
onPointerDown = action((e: PointerEvent) => {
- LinkDocPreview.Clear();
+ LinkInfo.Clear();
if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) {
this.clear();
}
@@ -47,14 +52,14 @@ export class LinkMenu extends React.Component<Props> {
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
const linkItems = Array.from(groups.entries()).map(group => (
- <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
+ <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
));
return linkItems.length ? linkItems : this.props.style ? [] : [<p key="none">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
};
render() {
- const sourceDoc = this.props.docView.rootDoc;
+ const sourceDoc = this.props.docView.Document;
const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c1a5a634c..91142b90b 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -7,7 +7,7 @@ import { LinkManager } from '../../util/LinkManager';
import { DocumentView } from '../nodes/DocumentView';
import './LinkMenu.scss';
import { LinkMenuItem } from './LinkMenuItem';
-import React = require('react');
+import * as React from 'react';
import { DocumentType } from '../../documents/DocumentTypes';
interface LinkMenuGroupProps {
@@ -46,14 +46,14 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
const groupItems = Array.from(set.keys()).map(linkDoc => {
const sourceDoc =
this.props.docView.anchorViewDoc ??
- (this.props.docView.rootDoc.type === DocumentType.LINK //
+ (this.props.docView.Document.type === DocumentType.LINK //
? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
? DocCast(linkDoc.link_anchor_1)
: DocCast(linkDoc.link_anchor_2)
: this.props.sourceDoc);
const destDoc = !sourceDoc
? undefined
- : this.props.docView.rootDoc.type === DocumentType.LINK
+ : this.props.docView.Document.type === DocumentType.LINK
? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
? DocCast(linkDoc.link_anchor_2)
: DocCast(linkDoc.link_anchor_1)
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e83f631a1..44c74236f 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.linkMenu-item {
// border-top: 0.5px solid $medium-gray;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index bf2a4e1a9..06073b52c 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,23 +1,24 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+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 { Doc } from '../../../fields/Doc';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
-import { undoBatch } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
-import React = require('react');
+import { undoBatch } from '../../util/UndoManager';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { DocumentType } from '../../documents/DocumentTypes';
interface LinkMenuItemProps {
groupType: string;
@@ -50,10 +51,13 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
}
@observer
-export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
+export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
-
_editRef = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable private _showMore: boolean = false;
@action toggleShowMore(e: React.PointerEvent) {
@@ -62,12 +66,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
@computed get sourceAnchor() {
- const ldoc = this.props.linkDoc;
- if (this.props.sourceDoc !== ldoc.link_anchor_1 && this.props.sourceDoc !== ldoc.link_anchor_2) {
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
+ const ldoc = this._props.linkDoc;
+ if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) {
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
}
- return this.props.sourceDoc;
+ return this._props.sourceDoc;
}
onEdit = (e: React.PointerEvent) => {
@@ -75,24 +79,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
this,
e,
e => {
- const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'embed');
+ const dragData = new DragManager.DocumentDragData([this._props.linkDoc], 'embed');
dragData.dropPropertiesToRemove = ['hidden'];
DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
return true;
},
emptyFunction,
action(() => {
- const trail = DocCast(this.props.docView.rootDoc.presentationTrail);
+ const trail = DocCast(this._props.docView.Document.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
} else {
- SelectionManager.SelectView(this.props.docView, false);
- LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc;
+ SelectionManager.SelectView(this._props.docView, false);
+ LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc;
LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- setTimeout(action(() => (SettingsManager.propertiesWidth = 250)));
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
}
}
})
@@ -106,43 +110,43 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
e => {
const eleClone: any = this._drag.current!.cloneNode(true);
eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
- this.props.clearLinkEditor?.();
+ StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+ this._props.clearLinkEditor?.();
return true;
},
emptyFunction,
() => {
- this.props.clearLinkEditor?.();
- if (this.props.itemHandler) {
- this.props.itemHandler?.(this.props.linkDoc);
+ this._props.clearLinkEditor?.();
+ if (this._props.itemHandler) {
+ this._props.itemHandler?.(this._props.linkDoc);
} else {
const focusDoc =
- Cast(this.props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this.props.sourceDoc
- ? Cast(this.props.linkDoc.link_anchor_1, Doc, null)
- : Cast(this.props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this.props.sourceDoc
- ? Cast(this.props.linkDoc.link_anchor_12, Doc, null)
- : undefined;
-
- if (focusDoc) this.props.docView.props.focus(focusDoc, { instant: true });
- LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, false);
+ Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc
+ ? Cast(this._props.linkDoc.link_anchor_1, Doc, null)
+ : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc
+ ? Cast(this._props.linkDoc.link_anchor_12, Doc, null)
+ : undefined;
+
+ if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
+ LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
}
}
);
};
- deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc))));
+ deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
@observable _hover = false;
render() {
- const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp;
+ const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
- const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title;
+ const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title;
const source =
- this.props.sourceDoc.type === DocumentType.RTF
- ? this.props.linkDoc.storedText
- ? StrCast(this.props.linkDoc.storedText).length > 17
- ? StrCast(this.props.linkDoc.storedText).substr(0, 18)
- : this.props.linkDoc.storedText
+ this._props.sourceDoc.type === DocumentType.RTF
+ ? this._props.linkDoc.storedText
+ ? StrCast(this._props.linkDoc.storedText).length > 17
+ ? StrCast(this._props.linkDoc.storedText).substr(0, 18)
+ : this._props.linkDoc.storedText
: undefined
: undefined;
@@ -154,7 +158,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
style={{
fontSize: this._hover ? 'larger' : undefined,
fontWeight: this._hover ? 'bold' : undefined,
- background: LinkManager.currentLink === this.props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
}}>
<div className="linkMenu-item-content expand-two">
<div
@@ -170,14 +174,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
</div>
<div
className="linkMenu-text"
- onPointerLeave={LinkDocPreview.Clear}
+ onPointerLeave={LinkInfo.Clear}
onPointerEnter={e =>
- this.props.linkDoc &&
- this.props.clearLinkEditor &&
- LinkDocPreview.SetLinkInfo({
- docProps: this.props.docView.props,
- linkSrc: this.props.sourceDoc,
- linkDoc: this.props.linkDoc,
+ this._props.linkDoc &&
+ this._props.clearLinkEditor &&
+ LinkInfo.SetLinkInfo({
+ docProps: this._props.docView._props,
+ linkSrc: this._props.sourceDoc,
+ linkDoc: this._props.linkDoc,
showHeader: false,
location: [(this._drag.current?.getBoundingClientRect().left ?? 100) + 40, (this._drag.current?.getBoundingClientRect().top ?? e.clientY) + 25],
noPreview: false,
@@ -194,10 +198,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
</div>
<p className="linkMenu-destination-title">
- {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
+ {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
</div>
- {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
+ {!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
</div>
<div className="linkMenu-item-buttons">
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 9a9a89732..5460a6daf 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -10,7 +10,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { SearchBox } from '../search/SearchBox';
import { DefaultStyleProvider } from '../StyleProvider';
import './LinkPopup.scss';
-import React = require('react');
+import * as React from 'react';
interface LinkPopupProps {
linkFrom?: () => Doc | undefined;
@@ -61,7 +61,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
<SearchBox
Document={Doc.MySearcher}
- DataDoc={Doc.MySearcher}
linkFrom={linkDoc}
linkCreateAnchor={this.props.linkCreateAnchor}
linkSearch={true}
@@ -74,7 +73,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
addDocument={undefined}
addDocTab={returnTrue}
pinToPres={emptyFunction}
- rootSelected={returnTrue}
+ rootSelected={returnFalse}
styleProvider={DefaultStyleProvider}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
index 9662b2fea..3e7f5be74 100644
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ b/src/client/views/linking/LinkRelationshipSearch.tsx
@@ -1,6 +1,6 @@
import { observer } from 'mobx-react';
import './LinkEditor.scss';
-import React = require('react');
+import * as React from 'react';
interface link_relationshipSearchProps {
results: string[] | undefined;
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index ca90f6a0f..f3cde3f5a 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { LinkManager } from '../../util/LinkManager';
@@ -293,7 +293,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
<DocumentView
ref={action((r: DocumentView | null) => (NewLightboxView._docView = r !== null ? r : undefined))}
Document={LightboxView.LightboxDoc}
- DataDoc={undefined}
PanelWidth={this.newLightboxWidth}
PanelHeight={this.newLightboxHeight}
LayoutTemplate={NewLightboxView.LightboxDocTemplate}
@@ -302,7 +301,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
styleProvider={DefaultStyleProvider}
ScreenToLocalTransform={this.newLightboxScreenToLocal}
renderDepth={0}
- rootSelected={returnTrue}
docViewPath={returnEmptyDoclist}
childFilters={this.docFilters}
childFiltersByRanges={returnEmptyFilter}
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
index 96846673b..23027808f 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -19,17 +19,19 @@ export const Recommendation = (props: IRecommendation) => {
if (source == 'Dash' && docId) {
const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement();
if (docView) {
- doc = docView.rootDoc;
+ doc = docView.Document;
}
} else if (data) {
switch (type) {
case 'YouTube':
console.log('create ', type, 'document');
- doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript });
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315 });
+ doc.transcript = transcript ? JSON.stringify(transcript) : undefined;
break;
case 'Video':
console.log('create ', type, 'document');
- doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript });
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315 });
+ doc.transcript = transcript ? JSON.stringify(transcript) : undefined;
break;
case 'Webpage':
console.log('create ', type, 'document');
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index d40537776..4337401e3 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
.audiobox-container {
width: 100%;
@@ -116,18 +116,18 @@
width: 10px;
}
- input[type="range"] {
+ input[type='range'] {
width: 50px;
-webkit-appearance: none;
background: none;
margin: 5px;
}
- input[type="range"]:focus {
+ input[type='range']:focus {
outline: none;
}
- input[type="range"]::-webkit-slider-runnable-track {
+ input[type='range']::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
@@ -136,7 +136,7 @@
border-radius: 3px;
}
- input[type="range"]::-webkit-slider-thumb {
+ input[type='range']::-webkit-slider-thumb {
box-shadow: 0;
border: 0;
height: 10px;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8d80f1364..567cf193e 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,8 +1,8 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
+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 { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
import { ComputedField } from '../../../fields/ScriptField';
@@ -17,7 +17,7 @@ import { undoBatch } from '../../util/UndoManager';
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import './AudioBox.scss';
import { DocFocusOptions } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
@@ -49,12 +49,18 @@ export enum media_state {
}
@observer
-export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(AudioBox, fieldKey);
}
+
public static Enabled = false;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
static topControlsHeight = 30; // height of upper controls above timeline
static bottomControlsHeight = 20; // height of lower controls below timeline
@@ -68,7 +74,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
_stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
_play: any = null; // timeout for playback
- @observable _stackedTimeline: CollectionStackedTimeline | null | undefined; // CollectionStackedTimeline ref
+ @observable _stackedTimeline: CollectionStackedTimeline | null | undefined = undefined; // CollectionStackedTimeline ref
@observable _finished: boolean = false; // has playback reached end of clip
@observable _volume: number = 1;
@observable _muted: boolean = false;
@@ -84,7 +90,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// if you get rid of it and set the value to 0 the timeline and waveform will set their bounds incorrectly
@computed get miniPlayer() {
- return this.props.PanelHeight() < 50;
+ return this._props.PanelHeight() < 50;
} // used to collapse timeline when node is shrunk
@computed get links() {
return LinkManager.Links(this.dataDoc);
@@ -94,7 +100,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@computed get path() {
// returns the path of the audio file
- const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || '';
+ const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
return path === nullAudio ? '' : path;
}
set mediaState(value) {
@@ -115,7 +121,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
if (this.path) {
this.mediaState = media_state.Paused;
this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -139,17 +145,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
const anchor = addAsAnnotation
? CollectionStackedTimeline.createAnchor(
- this.rootDoc,
+ this.Document,
this.dataDoc,
this.annotationKey,
- this._ele?.currentTime || Cast(this.props.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 === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
undefined,
undefined,
addAsAnnotation
- ) || this.rootDoc
- : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.rootDoc });
+ ) || this.Document
+ : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.Document);
return anchor;
};
@@ -186,13 +192,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._ele.play();
this.mediaState = media_state.Playing;
this.addCurrentlyPlaying();
- this._play = setTimeout(() => {
- // need to keep track of if end of clip is reached so on next play, clip restarts
- if (fullPlay) this._finished = true;
- // removes from currently playing if playback has reached end of range marker
- else this.removeCurrentlyPlaying();
- this.Pause();
- }, (end - start) * 1000);
+ this._play = setTimeout(
+ () => {
+ // need to keep track of if end of clip is reached so on next play, clip restarts
+ if (fullPlay) this._finished = true;
+ // removes from currently playing if playback has reached end of range marker
+ else this.removeCurrentlyPlaying();
+ this.Pause();
+ },
+ (end - start) * 1000
+ );
} else {
this.Pause();
}
@@ -202,7 +211,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -212,7 +221,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -240,7 +249,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
if (!(result instanceof Error)) {
- this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
+ this.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._recordStart = new Date().getTime();
@@ -363,17 +372,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
returnFalse,
returnFalse,
action(() => {
- const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
+ const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
const textField = Doc.LayoutFieldKey(newDoc);
Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
- Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
- if (Doc.IsInMyOverlay(this.rootDoc)) {
- newDoc.overlayX = this.rootDoc.x;
- newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+ Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
+ if (Doc.IsInMyOverlay(this.Document)) {
+ newDoc.overlayX = this.Document.x;
+ newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
Doc.AddToMyOverlay(newDoc);
} else {
- this.props.addDocument?.(newDoc);
+ this._props.addDocument?.(newDoc);
}
}),
false
@@ -424,7 +433,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// plays link
playLink = (link: Doc, options: DocFocusOptions) => {
- if (link.annotationOn === this.rootDoc) {
+ if (link.annotationOn === this.Document) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
} else {
@@ -449,9 +458,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
@action
- timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
- timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
+ timelineScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
@@ -460,8 +469,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
isActiveChild = () => this._isAnyChildContentActive;
// timeline dimensions
- timelineWidth = () => this.props.PanelWidth();
- timelineHeight = () => this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
+ timelineWidth = () => this._props.PanelWidth();
+ timelineHeight = () => this._props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
// ends trim, hides trim controls and displays new clip
@undoBatch
@@ -555,7 +564,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._dropDisposer = DragManager.MakeDropTarget(
r,
(e, de) => {
- const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const [xp, yp] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
},
this.layoutDoc,
@@ -597,7 +606,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="audiobox-file"
style={{
- pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? 'all' : 'none',
+ pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none',
flexDirection: this.miniPlayer ? 'row' : 'column',
justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
}}>
@@ -670,7 +679,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="audiobox-timecodes">
<div className="timecode-current">{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._layout_currentTimecode) - NumCast(this.timeline.clipStart)))}</div>
{this.miniPlayer ? (
- <div>/</div>
+ <div />
) : (
<div className="bottom-controls-middle">
<FontAwesomeIcon icon="search-plus" />
@@ -699,13 +708,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return (
<CollectionStackedTimeline
ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
- {...this.props}
+ {...this._props}
CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.path}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* audioStart */}
endTag={'_timecodeToHide' /* audioEnd */}
bringToFront={emptyFunction}
@@ -719,7 +728,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.timelineScreenToLocal}
Play={this.Play}
Pause={this.Pause}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
isAnyChildContentActive={this.isAnyChildContentActive}
playLink={this.playLink}
PanelWidth={this.timelineWidth}
@@ -734,7 +743,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return (
<audio
ref={this.setRef}
- className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}
+ className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
<source src={this.path} type="audio/mpeg" />
Not supported.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 421d431b3..548734dab 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,20 +1,21 @@
-import { action, computed, observable, trace } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../../fields/Doc';
+import * as React from 'react';
+import { OmitKeys, numberRange } from '../../../Utils';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { numberRange, OmitKeys } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
-import React = require('react');
export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
x: number;
@@ -23,6 +24,7 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
width: number;
height: number;
zIndex?: number;
+ autoDim?: number; // 1 means use Panel Width/Height, 0 means use width/height
rotation?: number;
color?: string;
backgroundColor?: string;
@@ -34,7 +36,11 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
CollectionFreeFormView: CollectionFreeFormView;
}
@observer
-export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps {
+export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable X = this.props.x;
@observable Y = this.props.y;
@observable Z = this.props.z;
@@ -46,6 +52,7 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
@observable Highlight = this.props.highlight;
@observable Width = this.props.width;
@observable Height = this.props.height;
+ @observable AutoDim = this.props.autoDim;
@observable Transition = this.props.transition;
@observable DataTransition = this.props.dataTransition;
CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
@@ -61,28 +68,30 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
w_X = () => this.X; // prettier-ignore
w_Y = () => this.Y; // prettier-ignore
w_Z = () => this.Z; // prettier-ignore
- w_ZIndex = () => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore
- w_Rotation = () => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore
+ w_ZIndex = () => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
+ w_Rotation = () => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
w_Opacity = () => this.Opacity; // prettier-ignore
- w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore
- w_Color = () => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore
+ w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
+ w_Color = () => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
w_Highlight = () => this.Highlight; // prettier-ignore
w_Width = () => this.Width; // prettier-ignore
w_Height = () => this.Height; // prettier-ignore
+ w_AutoDim = () => this.AutoDim; // prettier-ignore
w_Transition = () => this.Transition; // prettier-ignore
w_DataTransition = () => this.DataTransition; // prettier-ignore
- PanelWidth = () => this.Width || this.props.PanelWidth?.(); // prettier-ignore
- PanelHeight = () => this.Height || this.props.PanelHeight?.(); // prettier-ignore
- @action
- componentDidUpdate() {
- this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower]));
+ PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+ PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
+ componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
+ super.componentDidUpdate(prevProps);
+ this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
}
render() {
const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
return (
<CollectionFreeFormDocumentView
- {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
+ {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
{...layoutProps}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
@@ -112,8 +121,12 @@ export interface CollectionFreeFormDocumentViewProps {
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
get displayName() { // this makes mobx trace() statements more descriptive
- return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
+ return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
} // prettier-ignore
public static animFields: { key: string; val?: number }[] = [
{ key: 'x' },
@@ -132,72 +145,90 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
get CollectionFreeFormView() {
- return this.props.CollectionFreeFormView;
+ return this._props.CollectionFreeFormView;
}
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
if (doc === this.layoutDoc) {
switch (property) {
- case StyleProp.Opacity: return this.props.w_Opacity(); // only change the opacity for this specific document, not its children
- case StyleProp.BackgroundColor: return this.props.w_BackgroundColor();
- case StyleProp.Color: return this.props.w_Color();
+ case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
+ case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
+ case StyleProp.Color: return this._props.w_Color();
} // prettier-ignore
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
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);
- return p;
- }, {} as { [val: string]: Opt<number> });
+ 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);
+ return p;
+ },
+ {} as { [val: string]: Opt<number> }
+ );
}
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);
- return p;
- }, {} as { [val: string]: Opt<string> });
+ 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);
+ return p;
+ },
+ {} as { [val: string]: Opt<string> }
+ );
}
public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
- const findexed = Cast(d[`${val}-indexed`], listSpec('string'), []).slice();
+ const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
findexed[timecode] = vals[val] as any as string;
- d[`${val}-indexed`] = new List<string>(findexed);
+ d[`${val}_indexed`] = new List<string>(findexed);
});
}
public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
- const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice();
+ const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
findexed[timecode] = vals[val] as any as number;
- d[`${val}-indexed`] = new List<number>(findexed);
+ d[`${val}_indexed`] = new List<number>(findexed);
});
}
+ public static gotoKeyFrame(doc: Doc, newFrame: number) {
+ if (doc) {
+ const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+ const currentFrame = Cast(doc._currentFrame, 'number', null);
+ if (currentFrame === undefined) {
+ doc._currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ }
+ CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
+ doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+ }
+ }
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)));
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!!!)
- targetDoc.activeFrame = ComputedField.MakeFunction('self.embedContainer?._currentFrame||0');
+ targetDoc.activeFrame = ComputedField.MakeFunction('this.embedContainer?._currentFrame||0');
targetDoc.dataTransition = 'inherit';
});
}
@action public float = () => {
- const topDoc = this.rootDoc;
- const containerDocView = this.props.docViewPath().lastElement();
+ const topDoc = this.Document;
+ const containerDocView = this._props.docViewPath().lastElement();
const screenXf = containerDocView?.screenToLocalTransform();
if (screenXf) {
SelectionManager.DeselectAll();
@@ -206,8 +237,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
topDoc.z = 0;
topDoc.x = spt[0];
topDoc.y = spt[1];
- this.props.removeDocument?.(topDoc);
- this.props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
+ 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]);
@@ -220,15 +251,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
};
nudge = (x: number, y: number) => {
- const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y);
- this.props.Document.x = this.props.w_X() + locX;
- this.props.Document.y = this.props.w_Y() + locY;
+ const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+ this._props.Document.x = this._props.w_X() + locX;
+ this._props.Document.y = this._props.w_Y() + locY;
};
screenToLocalTransform = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
- .translate(-this.props.w_X(), -this.props.w_Y())
- .rotateDeg(-(this.props.w_Rotation?.() || 0));
+ .translate(-this._props.w_X(), -this._props.w_Y())
+ .rotateDeg(-(this._props.w_Rotation?.() || 0));
returnThis = () => this;
/// this indicates whether the doc view is activated because of its relationshop to a group
@@ -238,26 +269,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
// undefined - this is not activated by a group
isGroupActive = () => {
if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
- const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
- return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
+ const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
render() {
TraceMobx();
- const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+ const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
return (
<div
className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
style={{
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
- transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`,
- transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()),
- zIndex: this.props.w_ZIndex?.(),
- display: this.props.w_Width?.() ? undefined : 'none',
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
+ transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
+ transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
+ zIndex: this._props.w_ZIndex?.(),
+ display: this._props.w_Width?.() ? undefined : 'none',
}}>
- {this.props.RenderCutoffProvider(this.props.Document) ? (
- <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} />
+ {this._props.RenderCutoffProvider(this._props.Document) ? (
+ <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
)}
@@ -265,3 +296,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
);
}
}
+ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
+ CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame);
+});
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
deleted file mode 100644
index d5f2a7ec7..000000000
--- a/src/client/views/nodes/ColorBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.colorBox-container, .colorBox-container-interactive {
- width:100%;
- height:100%;
- position: relative;
- pointer-events: none;
- transform-origin: top left;
-
- .sketch-picker {
- div {
- cursor: crosshair;
- }
- .flexbox-fix {
- cursor: pointer;
- div {
- cursor:pointer;
- }
- }
- }
-}
-.colorBox-container-interactive {
- pointer-events:all;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
deleted file mode 100644
index 23ffd1080..000000000
--- a/src/client/views/nodes/ColorBox.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React = require('react');
-import { action } from 'mobx';
-import { observer } from 'mobx-react';
-import { ColorState, SketchPicker } from 'react-color';
-import { Doc } from '../../../fields/Doc';
-import { Height } from '../../../fields/DocSymbols';
-import { InkTool } from '../../../fields/InkField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { DashColor } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
-import './ColorBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { RichTextMenu } from './formattedText/RichTextMenu';
-
-@observer
-export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(ColorBox, fieldKey);
- }
-
- @undoBatch
- @action
- static switchColor(color: ColorState) {
- SetActiveInkColor(color.hex);
-
- SelectionManager.Views().map(view => {
- const targetDoc =
- view.props.Document.dragFactory instanceof Doc
- ? view.props.Document.dragFactory
- : view.props.Document.layout instanceof Doc
- ? view.props.Document.layout
- : view.props.Document.isTemplateForField
- ? view.props.Document
- : Doc.GetProto(view.props.Document);
- if (targetDoc) {
- if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {
- // this situation typically occurs when you have a link dot
- targetDoc.backgroundColor = color.hex; // bcz: don't know how to change the color of an inline template...
- } else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== '') {
- Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + '-color'] = color.hex;
- } else {
- Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ''); // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
- }
- }
- });
- }
-
- render() {
- const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.rootDoc._height), this.props.PanelWidth() / NumCast(this.rootDoc._width));
- return (
- <div
- className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
- onClick={e => e.stopPropagation()}
- style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
- <SketchPicker
- onChange={c => Doc.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
- color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- />
-
- <div style={{ width: this.props.PanelWidth() / scaling, display: 'flex', paddingTop: '10px' }}>
- <div> {ActiveInkWidth()}</div>
- <input
- type="range"
- defaultValue={ActiveInkWidth()}
- min={1}
- max={100}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- SetActiveInkWidth(e.target.value);
- SelectionManager.Views()
- .filter(i => StrCast(i.rootDoc.type) === DocumentType.INK)
- .map(i => (i.rootDoc.stroke_width = Number(e.target.value)));
- }}
- />
- </div>
- </div>
- );
- }
-}
-
-ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
- return DashColor(c1).mix(DashColor(c2), weight);
-});
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ff394e5f5..de382fca5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,10 +1,11 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+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 { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -13,7 +14,6 @@ import './ComparisonBox.scss';
import { DocumentView, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
-import React = require('react');
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -21,6 +21,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable _animating = '';
@@ -28,10 +32,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return NumCast(this.layoutDoc[this.clipWidthKey], 50);
}
get clipWidthKey() {
- return '_' + this.props.fieldKey + '_clipWidth';
+ return '_' + this._props.fieldKey + '_clipWidth';
}
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
@@ -44,7 +48,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
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.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+ 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);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
@@ -72,7 +76,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
if (this._isAnyChildContentActive) return;
this._animating = 'all 200ms';
// on click, animate slider movement to the targetWidth
- this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this.props.PanelWidth();
+ this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
setTimeout(
action(() => (this._animating = '')),
200
@@ -84,27 +88,27 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
@action
private onPointerMove = ({ movementX }: PointerEvent) => {
- const width = movementX * this.props.ScreenToLocalTransform().Scale + (this.clipWidth / 100) * this.props.PanelWidth();
- if (width && width > 5 && width < this.props.PanelWidth()) {
- this.layoutDoc[this.clipWidthKey] = (width * 100) / this.props.PanelWidth();
+ const width = movementX * this._props.ScreenToLocalTransform().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
+ if (width && width > 5 && width < this._props.PanelWidth()) {
+ this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth();
}
return false;
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
- title: 'CompareAnchor:' + this.rootDoc.title,
+ title: 'CompareAnchor:' + this.Document.title,
// set presentation timing properties for restoring view
presentation_transition: 1000,
- annotationOn: this.rootDoc,
+ annotationOn: this.Document,
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
/* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
return anchor;
}
- return this.rootDoc;
+ return this.Document;
};
@undoBatch
@@ -146,7 +150,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
- return this.props.styleProvider?.(doc, props, property);
+ 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);
@@ -171,9 +175,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return targetDoc ? (
<>
<DocumentView
- {...this.props}
+ {...this._props}
Document={targetDoc}
- DataDoc={undefined}
+ TemplateDataDocument={undefined}
moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
NativeWidth={returnZero}
@@ -181,7 +185,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
isContentActive={emptyFunction}
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- styleProvider={this._isAnyChildContentActive ? this.props.styleProvider : this.docStyleProvider}
+ styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider}
hideLinkButton={true}
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
@@ -195,15 +199,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
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)}>
+ <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 */}>
- {displayBox(`${this.fieldKey}_2`, 1, this.props.PanelWidth() - 3)}
+ <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+ {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
<div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
{displayBox(`${this.fieldKey}_1`, 0, 0)}
</div>
@@ -212,9 +216,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
className="slide-bar"
style={{
left: `calc(${this.clipWidth + '%'} - 0.5px)`,
- cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined,
+ cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
}}
- onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+ onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
>
<div className="slide-handle" />
</div>
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 4eb241380..56c130e0f 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -52,10 +52,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
TraceMobx();
- return <div className="dataVizBox-annotationLayer" style={{ height: this.props.PanelHeight(), width: this.props.PanelWidth() }} ref={this._annotationLayer} />;
+ return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
}
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._freeform_scale, 1) <= NumCast(this.rootDoc.freeform_scaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -75,7 +75,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
finishMarquee = () => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
- this.props.select(false);
+ this._props.select(false);
};
savedAnnotations = () => this._savedAnnotations;
@@ -90,7 +90,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
- var records = DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
+ var records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
}
@@ -131,15 +131,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
const anchor = !pinProps
- ? this.rootDoc
+ ? this.Document
: this._vizRenderer?.getAnchor(pinProps) ??
visibleAnchor ??
Docs.Create.ConfigDocument({
- title: 'ImgAnchor:' + this.rootDoc.title,
+ title: 'ImgAnchor:' + this.Document.title,
config_panX: NumCast(this.layoutDoc._freeform_panX),
config_panY: NumCast(this.layoutDoc._freeform_panY),
config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
- annotationOn: this.rootDoc,
+ 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*/
@@ -193,8 +193,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
- top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+ display: !this._props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
@@ -212,11 +212,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
e,
(e, down, delta) =>
runInAction(() => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
- const fullWidth = this.props.width;
+ const fullWidth = this._props.width;
const mapWidth = fullWidth! - this.sidebarWidth();
if (this.sidebarWidth() + localDelta[0] > 0) {
this.layoutDoc._layout_showSidebar = true;
@@ -241,9 +241,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
}
@computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
- sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.SidebarShown) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
@@ -251,31 +251,32 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
componentDidMount() {
- this.props.setContentView?.(this);
- if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData();
+ this._props.setContentView?.(this);
+ if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
}
fetchData = () => {
- DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+ 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.rootDoc[this.fieldKey]).url.href, res))));
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
};
// toggles for user to decide which chart type to view the data in
@computed get renderVizView() {
- const scale = this.props.NativeDimScaling?.() || 1;
+ const scale = this._props.NativeDimScaling?.() || 1;
const sharedProps = {
- rootDoc: this.rootDoc,
+ Document: this.Document,
layoutDoc: this.layoutDoc,
records: this.records,
axes: this.axes,
- height: (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9,
- 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() / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} />;
case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
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)}
@@ -285,9 +286,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if ((this.props.Document._freeform_scale || 1) !== 1) return;
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
- this.props.select(false);
+ if ((this.Document._freeform_scale || 1) !== 1) return;
+ if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ this._props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
const target = e.target as any;
@@ -306,7 +307,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
onSelectEnd = (e: PointerEvent): void => {
- this.props.select(false);
+ this._props.select(false);
document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
@@ -341,7 +342,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
current.push(row)
}
- DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, current )
+ DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current )
}
render() {
@@ -350,7 +351,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.updateSchemaViz();
}
- const scale = this.props.NativeDimScaling?.() || 1;
+ const scale = this._props.NativeDimScaling?.() || 1;
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>
@@ -359,7 +360,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
className="dataViz-box"
onPointerDown={this.marqueeDown}
style={{
- pointerEvents: this.props.isContentActive() === true ? 'all' : 'none',
+ pointerEvents: this._props.isContentActive() === true ? 'all' : 'none',
width: `${100 / scale}%`,
height: `${100 / scale}%`,
transform: `scale(${scale})`,
@@ -377,15 +378,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{/* <CollectionFreeFormView
ref={this._ffref}
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
- renderDepth={this.props.renderDepth - 1}
+ renderDepth={this._props.renderDepth - 1}
fieldKey={this.annotationKey}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
isAnnotationOverlay={true}
annotationLayerHostsContent={false}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -402,9 +403,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onPointerDown={this.onPointerDown}>
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
fieldKey={this.fieldKey}
- rootDoc={this.rootDoc}
+ rootDoc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth={true}
@@ -421,13 +422,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
- rootDoc={this.rootDoc}
+ rootDoc={this.Document}
getPageFromScroll={undefined}
anchorMenuClick={this.anchorMenuClick}
scrollTop={0}
- annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+ annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
addDocument={this.sidebarAddDocument}
- docView={this.props.DocumentView!}
+ docView={this.DocumentView!}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
selectionText={returnEmptyString}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index bef3cced5..2f7dd0487 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,4 +1,4 @@
-@import '../../../global/globalCssVariables';
+@import '../../../global/globalCssVariables.module.scss';
.chart-container {
display: flex;
flex-direction: column;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index eca77f0f3..227c993c7 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaFillDrip } from 'react-icons/fa';
@@ -10,14 +10,14 @@ import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
-import { LinkManager } from '../../../../util/LinkManager';
import { undoable } from '../../../../util/UndoManager';
import { PinProps, PresBox } from '../../trails';
import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
export interface HistogramProps {
- rootDoc: Doc;
+ Document: Doc;
layoutDoc: Doc;
axes: string[];
records: { [key: string]: any }[];
@@ -34,7 +34,7 @@ export interface HistogramProps {
}
@observer
-export class Histogram extends React.Component<HistogramProps> {
+export class Histogram extends ObservableReactComponent<HistogramProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _histogramRef: React.RefObject<HTMLDivElement> = React.createRef();
private _histogramSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@@ -46,46 +46,51 @@ export class Histogram extends React.Component<HistogramProps> {
private selectedData: any = undefined; // Selection of selected bar
private hoverOverData: any = undefined; // Selection of bar being hovered over
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get _tableDataIds() {
- return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+ return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
}
// returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
@computed get _tableData() {
- return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
// filters all data to just display selected data if brushed (created from an incoming link)
@computed get _histogramData() {
- if (this.props.axes.length < 1) return [];
- if (this.props.axes.length < 2) {
- var ax0 = this.props.axes[0];
- if (/\d/.test(this.props.records[0][ax0])) {
+ if (this._props.axes.length < 1) return [];
+ if (this._props.axes.length < 2) {
+ var ax0 = this._props.axes[0];
+ if (/\d/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
- return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
+ return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
- var ax0 = this.props.axes[0];
- var ax1 = this.props.axes[1];
- if (/\d/.test(this.props.records[0][ax0])) {
+ var ax0 = this._props.axes[0];
+ var ax1 = this._props.axes[1];
+ if (/\d/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
- if (/\d/.test(this.props.records[0][ax1])) {
+ if (/\d/.test(this._props.records[0][ax1])) {
this.numericalYData = true;
}
- return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
+ return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
}
@computed get defaultGraphTitle() {
- var ax0 = this.props.axes[0];
- var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.records[0][ax1]) || !this.numericalYData) {
+ var ax0 = this._props.axes[0];
+ var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
return ax0 + ' Histogram';
} else return ax0 + ' by ' + ax1 + ' Histogram';
}
@computed get parentViz() {
- return DocCast(this.props.rootDoc.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+ return DocCast(this._props.Document.dataViz_parentViz);
+ // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+ // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
// .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -115,16 +120,16 @@ export class Histogram extends React.Component<HistogramProps> {
const anchor = Docs.Create.ConfigDocument({
title: 'histogram doc selection' + this._currSelected,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
return anchor;
};
@computed get height() {
- return this.props.height - this.props.margin.top - this.props.margin.bottom;
+ return this._props.height - this._props.margin.top - this._props.margin.bottom;
}
@computed get width() {
- return this.props.width - this.props.margin.left - this.props.margin.right;
+ return this._props.width - this._props.margin.left - this._props.margin.right;
}
// cleans data by converting numerical data to numbers and taking out empty cells
@@ -212,10 +217,10 @@ export class Histogram extends React.Component<HistogramProps> {
.select(this._histogramRef.current)
.append('svg')
.attr('class', 'graph')
- .attr('width', width + this.props.margin.right + this.props.margin.left)
- .attr('height', height + this.props.margin.top + this.props.margin.bottom)
+ .attr('width', width + this._props.margin.right + this._props.margin.left)
+ .attr('height', height + this._props.margin.top + this._props.margin.bottom)
.append('g')
- .attr('transform', 'translate(' + this.props.margin.left + ',' + this.props.margin.top + ')'));
+ .attr('transform', 'translate(' + this._props.margin.left + ',' + this._props.margin.top + ')'));
var x = d3
.scaleLinear()
.domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
@@ -383,7 +388,7 @@ export class Histogram extends React.Component<HistogramProps> {
)
.attr('fill', d => {
var barColor;
- const barColors = StrListCast(this.props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
+ 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];
else {
@@ -391,24 +396,24 @@ export class Histogram extends React.Component<HistogramProps> {
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
- return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.dataViz_histogram_defaultColor);
+ return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
});
};
@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);
+ 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));
barColors.push(StrCast(barName + '::' + color));
};
@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, ''));
+ 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 barColors = Cast(this.props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
+ 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));
};
@@ -417,7 +422,7 @@ export class Histogram extends React.Component<HistogramProps> {
if (svg)
svg.selectAll('rect').attr('fill', (d: any) => {
var barColor;
- const barColors = StrListCast(this.props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
+ 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];
else {
@@ -425,7 +430,7 @@ export class Histogram extends React.Component<HistogramProps> {
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
- return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.dataViz_histogram_defaultColor);
+ return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
});
};
@@ -434,14 +439,14 @@ export class Histogram extends React.Component<HistogramProps> {
this._histogramData;
var curSelectedBarName = '';
var titleAccessor: any = '';
- if (this.props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this.props.axes[0] + '-' + this.props.axes[1];
- else if (this.props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + 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>();
+ if (this._props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + 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';
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 =>
key //
@@ -451,17 +456,17 @@ export class Histogram extends React.Component<HistogramProps> {
selected = selected.substring(0, selected.length - 2) + ' }';
}
var selectedBarColor;
- var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
if (this._histogramData.length > 0 || !this.parentViz) {
- return this.props.axes.length >= 1 ? (
- <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}>
+ return this._props.axes.length >= 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])}
+ 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'}
@@ -473,9 +478,9 @@ export class Histogram extends React.Component<HistogramProps> {
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')}
+ 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')}
size={Size.XSMALL}
/>
</div>
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 007d0259c..4b0df0457 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,6 +1,6 @@
import { Button, EditableText, Size } from 'browndash-components';
import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
@@ -14,6 +14,7 @@ import { PinProps, PresBox } from '../../trails';
import { DataVizBox } from '../DataVizBox';
import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
export interface DataPoint {
x: number;
@@ -24,7 +25,7 @@ export interface SelectedDataPoint extends DataPoint {
}
export interface LineChartProps {
vizBox: DataVizBox;
- rootDoc: Doc;
+ Document: Doc;
layoutDoc: Doc;
axes: string[];
records: { [key: string]: any }[];
@@ -41,33 +42,38 @@ export interface LineChartProps {
}
@observer
-export class LineChart extends React.Component<LineChartProps> {
+export class LineChart extends ObservableReactComponent<LineChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@observable _currSelected: SelectedDataPoint | undefined = undefined;
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
+ constructor(props:any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get _tableDataIds() {
- return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+ return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
}
// returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
@computed get _tableData() {
- return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ 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));
+ 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));
}
@computed get graphTitle() {
- return this.props.axes[1] + ' vs. ' + this.props.axes[0] + ' Line Chart';
+ return this._props.axes[1] + ' vs. ' + this._props.axes[0] + ' Line Chart';
}
@computed get parentViz() {
- return DocCast(this.props.rootDoc.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+ return DocCast(this._props.Document.dataViz_parentViz);
+ // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
// .filter(link => {
- // return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
+ // return link.link_anchor_1 == this._props.Document.dataViz_parentViz;
// }) // get links where this chart doc is the target of the link
// .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -75,7 +81,7 @@ export class LineChart extends React.Component<LineChartProps> {
// return selected x and y axes
// otherwise, use the selection of whatever is linked to us
const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
- const highlitedRowIds = NumListCast(incomingVizBox?.rootDoc?.dataViz_highlitedRows);
+ const highlitedRowIds = NumListCast(incomingVizBox?.layoutDoc?.dataViz_highlitedRows);
return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -95,7 +101,7 @@ export class LineChart extends React.Component<LineChartProps> {
{ fireImmediately: true }
);
this._disposers.annos = reaction(
- () => DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']),
+ () => DocListCast(this._props.dataDoc[this._props.fieldKey + '_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
@@ -115,7 +121,7 @@ export class LineChart extends React.Component<LineChartProps> {
// redraw annotations when the chart data has changed, or the local or inherited selection has changed
this.clearAnnotations();
selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
- incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
+ incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this._props.axes[0]]), Number(record[this._props.axes[1]])));
},
{ fireImmediately: true }
);
@@ -171,23 +177,23 @@ export class LineChart extends React.Component<LineChartProps> {
//
title: 'line doc selection' + this._currSelected?.x,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ 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;
return anchor;
};
@computed get height() {
- return this.props.height - this.props.margin.top - this.props.margin.bottom;
+ return this._props.height - this._props.margin.top - this._props.margin.bottom;
}
@computed get width() {
- return this.props.width - this.props.margin.left - this.props.margin.right;
+ return this._props.width - this._props.margin.left - this._props.margin.right;
}
@computed get defaultGraphTitle() {
- var ax0 = this.props.axes[0];
- var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
+ var ax0 = this._props.axes[0];
+ var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Line Chart';
} else return ax1 + ' by ' + ax0 + ' Line Chart';
}
@@ -211,7 +217,7 @@ export class LineChart extends React.Component<LineChartProps> {
// TODO: nda - get rid of svg element in the list?
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>) {
@@ -246,7 +252,7 @@ export class LineChart extends React.Component<LineChartProps> {
const yScale = scaleCreatorNumerical(0, yMax, height, 0);
// adding svg
- const margin = this.props.margin;
+ const margin = this._props.margin;
const svg = (this._lineChartSvg = d3
.select(this._lineChartRef.current)
.append('svg')
@@ -318,7 +324,7 @@ export class LineChart extends React.Component<LineChartProps> {
svg.append('text')
.attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')')
.style('text-anchor', 'middle')
- .text(this.props.axes[0]);
+ .text(this._props.axes[0]);
svg.append('text')
.attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
@@ -326,7 +332,7 @@ export class LineChart extends React.Component<LineChartProps> {
.attr('height', 20)
.attr('width', 20)
.style('text-anchor', 'middle')
- .text(this.props.axes[1]);
+ .text(this._props.axes[1]);
};
private updateTooltip(
@@ -347,18 +353,18 @@ export class LineChart extends React.Component<LineChartProps> {
render() {
var titleAccessor: any = '';
- if (this.props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this.props.axes[0] + '-' + this.props.axes[1];
- else if (this.props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this.props.axes[0];
- if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
- const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none';
+ if (this._props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0];
+ if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+ const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
if (this._lineChartData.length > 0 || !this.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]]) ? (
+ 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])}
+ 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'}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 4622b470a..e644870da 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,6 +1,7 @@
+import { Checkbox } from '@mui/material';
import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaFillDrip } from 'react-icons/fa';
@@ -12,10 +13,10 @@ import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
import { PinProps, PresBox } from '../../trails';
import './Chart.scss';
-import { Checkbox } from '@material-ui/core';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
export interface PieChartProps {
- rootDoc: Doc;
+ Document: Doc;
layoutDoc: Doc;
axes: string[];
records: { [key: string]: any }[];
@@ -32,7 +33,7 @@ export interface PieChartProps {
}
@observer
-export class PieChart extends React.Component<PieChartProps> {
+export class PieChart extends ObservableReactComponent<PieChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@@ -41,44 +42,49 @@ export class PieChart extends React.Component<PieChartProps> {
private hoverOverData: any = undefined; // Selection of slice being hovered over
@observable _currSelected: any | undefined = undefined; // Object of selected slice
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get _tableDataIds() {
- return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+ return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
}
// returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
@computed get _tableData() {
- return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
// organized by specified number percentages/ratios if one column is selected and it contains numbers
// otherwise, assume data is organized by categories
@computed get byCategory() {
- return !/\d/.test(this.props.records[0][this.props.axes[0]]) || this.props.layoutDoc.dataViz_pie_asHistogram;
+ return !/\d/.test(this._props.records[0][this._props.axes[0]]) || this._props.layoutDoc.dataViz_pie_asHistogram;
}
// filters all data to just display selected data if brushed (created from an incoming link)
@computed get _pieChartData() {
- if (this.props.axes.length < 1) return [];
+ if (this._props.axes.length < 1) return [];
- const ax0 = this.props.axes[0];
- if (this.props.axes.length < 2) {
- return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
+ const ax0 = this._props.axes[0];
+ if (this._props.axes.length < 2) {
+ return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
- const ax1 = this.props.axes[1];
- return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
+ const ax1 = this._props.axes[1];
+ return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
}
@computed get defaultGraphTitle() {
- var ax0 = this.props.axes[0];
- var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
+ var ax0 = this._props.axes[0];
+ var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Pie Chart';
}
return ax1 + ' by ' + ax0 + ' Pie Chart';
}
@computed get parentViz() {
- return DocCast(this.props.rootDoc.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+ return DocCast(this._props.Document.dataViz_parentViz);
+ // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+ // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
// .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -101,16 +107,16 @@ export class PieChart extends React.Component<PieChartProps> {
//
title: 'piechart doc selection' + this._currSelected,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
return anchor;
};
@computed get height() {
- return this.props.height - this.props.margin.top - this.props.margin.bottom;
+ return this._props.height - this._props.margin.top - this._props.margin.bottom;
}
@computed get width() {
- return this.props.width - this.props.margin.left - this.props.margin.right;
+ return this._props.width - this._props.margin.left - this._props.margin.right;
}
// cleans data by converting numerical data to numbers and taking out empty cells
@@ -181,7 +187,7 @@ export class PieChart extends React.Component<PieChartProps> {
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;
+ var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
// converts data into Objects
var data = this.data(dataSet);
@@ -209,10 +215,10 @@ export class PieChart extends React.Component<PieChartProps> {
.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)
+ .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 + ')');
+ 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);
@@ -249,7 +255,7 @@ export class PieChart extends React.Component<PieChartProps> {
} catch (error) {}
possibleDataPointVals.push(dataPointVal);
});
- const sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+ const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
arcs.append('path')
.attr('fill', (d, i) => {
var dataPoint;
@@ -261,7 +267,7 @@ export class PieChart extends React.Component<PieChartProps> {
}
var sliceColor;
if (dataPoint) {
- const sliceTitle = dataPoint[this.props.axes[0]];
+ 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]));
}
@@ -277,6 +283,7 @@ export class PieChart extends React.Component<PieChartProps> {
return 'slice';
}
)
+ // @ts-ignore
.attr('d', arc)
.on('click', onPointClick)
.on('mouseover', onHover)
@@ -308,10 +315,10 @@ export class PieChart extends React.Component<PieChartProps> {
@action changeSelectedColor = (color: string) => {
this.curSliceSelected.attr('fill', color);
- const sliceTitle = this._currSelected[this.props.axes[0]];
+ const sliceTitle = this._currSelected[this._props.axes[0]];
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);
+ 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);
});
@@ -319,20 +326,20 @@ export class PieChart extends React.Component<PieChartProps> {
};
@action changeHistogramCheckBox = () => {
- this.props.layoutDoc.dataViz_pie_asHistogram = !this.props.layoutDoc.dataViz_pie_asHistogram;
+ this._props.layoutDoc.dataViz_pie_asHistogram = !this._props.layoutDoc.dataViz_pie_asHistogram;
this.drawChart(this._pieChartData, this.width, this.height);
};
render() {
var titleAccessor: any = '';
- if (this.props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this.props.axes[0] + '-' + this.props.axes[1];
- else if (this.props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + 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>();
+ if (this._props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + 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 = '';
if (this._currSelected) {
- const sliceTitle = this._currSelected[this.props.axes[0]];
+ const sliceTitle = this._currSelected[this._props.axes[0]];
curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
selected = '{ ';
Object.keys(this._currSelected).map(key => {
@@ -342,19 +349,19 @@ export class PieChart extends React.Component<PieChartProps> {
selected += ' }';
} else selected = 'none';
var selectedSliceColor;
- var sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+ var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
sliceColors.forEach(each => {
if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
});
if (this._pieChartData.length > 0 || !this.parentViz) {
- return this.props.axes.length >= 1 ? (
- <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}>
+ return this._props.axes.length >= 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])}
+ 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'}
@@ -362,9 +369,9 @@ export class PieChart extends React.Component<PieChartProps> {
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 }}>
- <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this.props.layoutDoc.dataViz_pie_asHistogram as boolean} />
+ {this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
+ <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}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 012021dda..16e66d0c3 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,5 +1,5 @@
import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
@@ -10,11 +10,11 @@ import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
-import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
import './Chart.scss';
-
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
+const { default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
interface TableBoxProps {
- rootDoc: Doc;
+ Document: Doc;
layoutDoc: Doc;
records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
@@ -31,13 +31,17 @@ interface TableBoxProps {
}
@observer
-export class TableBox extends React.Component<TableBoxProps> {
+export class TableBox extends ObservableReactComponent<TableBoxProps> {
_inputChangedDisposer?: IReactionDisposer;
_containerRef: HTMLDivElement | null = null;
@observable _scrollTop = -1;
@observable _tableHeight = 0;
@observable _tableContainerHeight = 0;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
componentDidMount() {
// if the tableData changes (ie., when records are selected by the parent (input) visulization),
@@ -49,17 +53,17 @@ export class TableBox extends React.Component<TableBoxProps> {
this._inputChangedDisposer?.();
}
@computed get _tableDataIds() {
- return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+ return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
}
// returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
@computed get _tableData() {
- return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
@computed get parentViz() {
- return DocCast(this.props.rootDoc.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+ return DocCast(this._props.Document.dataViz_parentViz);
+ // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+ // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
// .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -69,33 +73,35 @@ export class TableBox extends React.Component<TableBoxProps> {
// updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
filterSelectedRowsDown = () => {
- const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
- this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
- const highlighted = NumListCast(this.props.layoutDoc.dataViz_highlitedRows);
- this.props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
+ const selected = NumListCast(this._props.layoutDoc.dataViz_selectedRows);
+ this._props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+ const highlighted = NumListCast(this._props.layoutDoc.dataViz_highlitedRows);
+ this._props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
};
@computed get viewScale() {
- return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
+ return this._props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
}
@computed get rowHeight() {
+ console.log('scale = ' + this.viewScale + ' table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
}
@computed get startID() {
return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0;
}
@computed get endID() {
+ console.log('start = ' + this.startID + ' container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
}
@action handleScroll = () => {
- if (!this.props.docView?.()?.ContentDiv?.hidden) {
+ if (!this._props.docView?.()?.ContentDiv?.hidden) {
this._scrollTop = this._containerRef?.scrollTop ?? 0;
}
};
@action
tableRowClick = (e: React.MouseEvent, rowId: number) => {
- const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
- const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ const highlited = Cast(this._props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+ const selected = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
if (e.metaKey) {
// highlighting a row
if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
@@ -119,26 +125,26 @@ export class TableBox extends React.Component<TableBoxProps> {
e,
e => {
// dragging off a column to create a brushed DataVizBox
- const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+ const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
const targetCreator = (annotationOn: Doc | undefined) => {
- const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
+ const embedding = Doc.MakeEmbedding(this._props.docView?.()!.Document!);
embedding._dataViz = DataVizView.TABLE;
embedding._dataViz_axes = new List<string>([col, col]);
- embedding._dataViz_parentViz = this.props.rootDoc;
+ embedding._dataViz_parentViz = this._props.Document;
embedding.annotationOn = annotationOn;
- embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
- embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
- embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+ embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this._props.layoutDoc.defaultHistogramColor;
+ embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
return embedding;
};
- if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ 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;
- // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
// e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -149,11 +155,11 @@ export class TableBox extends React.Component<TableBoxProps> {
},
emptyFunction,
action(e => {
- const newAxes = this.props.axes;
+ const newAxes = this._props.axes;
if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
else if (newAxes.length > 1) newAxes[1] = col;
else newAxes.push(col);
- this.props.selectAxes(newAxes);
+ this._props.selectAxes(newAxes);
})
);
};
@@ -166,14 +172,14 @@ export class TableBox extends React.Component<TableBoxProps> {
style={{width: this.props.width+this.props.margin.right}}
tabIndex={0}
onKeyDown={e => {
- if (this.props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
e.stopPropagation();
- this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
+ this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
}
}}>
<div className="selectAll-buttons">
- <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
- <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
+ <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
+ <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
</div>
<div
className={`tableBox-container ${this.columns[0]}`}
@@ -181,7 +187,7 @@ export class TableBox extends React.Component<TableBoxProps> {
onScroll={this.handleScroll}
ref={action((r: HTMLDivElement | null) => {
this._containerRef = r;
- if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
this._tableContainerHeight = r.getBoundingClientRect().height ?? 0;
r.addEventListener(
'wheel', // 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)
@@ -196,7 +202,7 @@ export class TableBox extends React.Component<TableBoxProps> {
<table
className="tableBox-table"
ref={action((r: HTMLTableElement | null) => {
- if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
this._tableHeight = r?.getBoundingClientRect().height ?? 0;
}
})}>
@@ -207,8 +213,8 @@ export class TableBox extends React.Component<TableBoxProps> {
<th
key={this.columns.indexOf(col)}
style={{
- color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined,
- background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+ color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this._props.axes.lastElement() === col ? 'darkred' : undefined,
+ background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this._props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
fontWeight: 'bolder',
border: '3px solid black',
}}
@@ -227,13 +233,13 @@ export class TableBox extends React.Component<TableBoxProps> {
className={`tableBox-row ${this.columns[0]}`}
onClick={e => this.tableRowClick(e, rowId)}
style={{
- background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+ background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
+ const colSelected = this._props.axes.length > 1 ? this._props.axes[0] == col || this._props.axes[1] == col : this._props.axes.length > 0 ? this._props.axes[0] == col : false;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
- <div className="tableBox-cell">{this.props.records[rowId][col]}</div>
+ <div className="tableBox-cell">{this._props.records[rowId][col]}</div>
</td>
);
})}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 8c8c987fe..5b2bf4774 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,24 +1,26 @@
-import { computed } from 'mobx';
+import { computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
+import * as XRegExp from 'xregexp';
+import { OmitKeys, Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { AclPrivate } from '../../../fields/DocSymbols';
+import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
import { Cast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyPath, OmitKeys, Without } from '../../../Utils';
-import { DirectoryImportBox } from '../../util/Import & Export/DirectoryImportBox';
+import { InkingStroke } from '../InkingStroke';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { CollectionView } from '../collections/CollectionView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
+import { CollectionCalendarView } from '../collections/CollectionCalendarView';
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
-import { CollectionView } from '../collections/CollectionView';
-import { InkingStroke } from '../InkingStroke';
import { PresElementBox } from '../nodes/trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
-import { ColorBox } from './ColorBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
import { DocumentViewProps } from './DocumentView';
@@ -26,27 +28,25 @@ import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
import { FontIconBox } from './FontIconBox/FontIconBox';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
-import { ImportElementBox } from './importBox/ImportElementBox';
import { KeyValueBox } from './KeyValueBox';
import { LabelBox } from './LabelBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkBox } from './LinkBox';
import { LoadingBox } from './LoadingBox';
import { MapBox } from './MapBox/MapBox';
+import { MapPushpinBox } from './MapBox/MapPushpinBox';
import { PDFBox } from './PDFBox';
import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox';
import { RecordingBox } from './RecordingBox';
import { ScreenshotBox } from './ScreenshotBox';
import { ScriptingBox } from './ScriptingBox';
-import { PresBox } from './trails/PresBox';
import { VideoBox } from './VideoBox';
import { WebBox } from './WebBox';
-import React = require('react');
-import XRegExp = require('xregexp');
-import { MapPushpinBox } from './MapBox/MapPushpinBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImportElementBox } from './importBox/ImportElementBox';
+import { PresBox } from './trails/PresBox';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -66,7 +66,6 @@ export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
interface HTMLtagProps {
Document: Doc;
- RootDoc: Doc;
htmltag: string;
onClick?: ScriptField;
onInput?: ScriptField;
@@ -90,18 +89,18 @@ interface HTMLtagProps {
export class HTMLtag extends React.Component<HTMLtagProps> {
click = (e: React.MouseEvent) => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
- clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling });
+ clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
};
onInput = (e: React.FormEvent<HTMLDivElement>) => {
const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
- onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent });
+ onInputScript?.script.run({ this: this.props.Document, self: this.props.Document, value: (e.target as any).textContent });
};
render() {
const style: { [key: string]: any } = {};
- const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
+ 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) => {
- // bcz: this executes a script to convert a propery expression string: { script } into a value
- return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+ // 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) => {
const p = (this.props as any)[prop] as string;
@@ -117,39 +116,38 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
@observer
-export class DocumentContentsView extends React.Component<
- DocumentViewProps & {
- isSelected: () => boolean;
- select: (ctrl: boolean) => void;
- NativeDimScaling?: () => number;
- setHeight?: (height: number) => void;
- layout_fieldKey: string;
- }
+export class DocumentContentsView extends ObservableReactComponent<
+ DocumentViewProps &
+ FieldViewProps & {
+ setHeight?: (height: number) => void;
+ layout_fieldKey: string;
+ }
> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@computed get layout(): string {
TraceMobx();
- if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
+ if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
if (!this.layoutDoc) return '<p>awaiting layout</p>';
- if (this.props.layout_fieldKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layout_fieldKey ? this.props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
- if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
+ if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+ const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return '<p>Loading layout</p>';
}
- get dataDoc() {
- const proto = this.props.DataDoc || Doc.GetProto(this.props.Document);
- return proto instanceof Promise ? undefined : proto;
- }
get layoutDoc() {
// bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
- // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
+ // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
const template: Doc =
- this.props.LayoutTemplate?.() ||
- (this.props.LayoutTemplateString && this.props.Document) ||
- (this.props.layout_fieldKey && StrCast(this.props.Document[this.props.layout_fieldKey]) && this.props.Document) ||
- Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
- return Doc.expandTemplateLayout(template, this.props.Document);
+ this._props.LayoutTemplate?.() ||
+ (this._props.LayoutTemplateString && this._props.Document) ||
+ (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
+ Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+ return Doc.expandTemplateLayout(template, this._props.Document);
}
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
@@ -163,24 +161,28 @@ export class DocumentContentsView extends React.Component<
'LayoutTemplate',
'dontCenter',
'contextMenuItems',
- 'onClick',
+ //'onClick', // don't need to omit this since it will be set
'onDoubleClick',
'onPointerDown',
'onPointerUp',
];
- const list = {
- ...OmitKeys(this.props, [...docOnlyProps], '').omit,
- RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
- Document: this.layoutDoc,
- DataDoc: this.dataDoc,
- onClick: onClick,
- onInput: onInput,
+ const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
+ const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
+ ...this._props,
+ Document: this.layoutDoc ?? this._props.Document,
+ TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc,
+ onClick: onClick as any as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag
+ onInput: onInput as any as React.FormEventHandler,
+ };
+ return {
+ props: {
+ ...OmitKeys(list, [...docOnlyProps], '').omit,
+ },
};
- return { props: list };
}
// componentWillUpdate(oldProps: any, newState: any) {
- // // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+ // // console.log("willupdate", oldProps, this._props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
// }
@computed get renderData() {
@@ -189,13 +191,13 @@ export class DocumentContentsView extends React.Component<
// replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv>
const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
- return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix;
+ return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
};
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
+ return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
};
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
@@ -227,7 +229,7 @@ export class DocumentContentsView extends React.Component<
TraceMobx();
const { bindings, layoutFrame } = this.renderData;
- return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
+ return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
<ObserverJsxParser
key={42}
blacklistedAttrs={emptyPath}
@@ -235,7 +237,6 @@ export class DocumentContentsView extends React.Component<
components={{
FormattedTextBox,
ImageBox,
- DirectoryImportBox,
FontIconBox,
LabelBox,
EquationBox,
@@ -243,6 +244,7 @@ export class DocumentContentsView extends React.Component<
CollectionFreeFormView,
CollectionDockingView,
CollectionSchemaView,
+ CollectionCalendarView,
CollectionView,
WebBox,
KeyValueBox,
@@ -255,7 +257,6 @@ export class DocumentContentsView extends React.Component<
PresElementBox,
SearchBox,
FunctionPlotBox,
- ColorBox,
DashWebRTCVideo,
LinkAnchorBox,
InkingStroke,
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index bccbd66e8..dfd610581 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -1,24 +1,34 @@
+import { Tooltip } from '@mui/material';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { DocumentView } from './DocumentView';
-import { DocumentManager } from '../../util/DocumentManager';
-import { Transformer, ts } from '../../util/Scripting';
+import { factory } from 'typescript';
import { Field } from '../../../fields/Doc';
-import { Tooltip } from '@material-ui/core';
-import { action, observable } from 'mobx';
import { Id } from '../../../fields/FieldSymbols';
-import { factory } from 'typescript';
-import { LightboxView } from '../LightboxView';
+import { DocumentManager } from '../../util/DocumentManager';
+import { Transformer, ts } from '../../util/Scripting';
import { SettingsManager } from '../../util/SettingsManager';
+import { LightboxView } from '../LightboxView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DocumentView } from './DocumentView';
+interface DocumentIconProps {
+ view: DocumentView;
+ index: number;
+}
@observer
-export class DocumentIcon extends React.Component<{ view: DocumentView; index: number }> {
+export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
@observable _hovered = false;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
static get DocViews() {
- return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v.props.docViewPath())) : DocumentManager.Instance.DocumentViews;
+ return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v._props.docViewPath())) : DocumentManager.Instance.DocumentViews;
}
render() {
- const view = this.props.view;
+ const view = this._props.view;
const { left, top, right, bottom } = view.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
return (
@@ -33,8 +43,8 @@ export class DocumentIcon extends React.Component<{ view: DocumentView; index: n
background: SettingsManager.userBackgroundColor,
transform: `translate(${(left + right) / 2}px, ${top}px)`,
}}>
- <Tooltip title={<>{this.props.view.rootDoc.title}</>}>
- <p>d{this.props.index}</p>
+ <Tooltip title={<>{this._props.view.Document.title}</>}>
+ <p>d{this._props.index}</p>
</Tooltip>
</div>
);
@@ -59,7 +69,7 @@ export class DocumentIconContainer extends React.Component {
const match = node.text.match(/d([0-9]+)/);
if (match) {
const m = parseInt(match[1]);
- const doc = DocumentIcon.DocViews[m].rootDoc;
+ const doc = DocumentIcon.DocViews[m].Document;
usedDocuments.add(m);
return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
}
@@ -74,7 +84,7 @@ export class DocumentIconContainer extends React.Component {
getVars() {
const docs = DocumentIcon.DocViews;
const capturedVariables: { [name: string]: Field } = {};
- usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].props.Document : `d${index}`));
+ usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`));
return capturedVariables;
},
};
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 6da0b73ba..b32b27e65 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.documentLinksButton-wrapper {
transform-origin: top left;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 50a7f5d7b..165057d21 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,22 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, runInAction } from 'mobx';
+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 { Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { Hypothesis } from '../../util/HypothesisUtils';
import { LinkManager } from '../../util/LinkManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import './DocumentLinksButton.scss';
import { DocumentView } from './DocumentView';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
import { PinProps } from './trails';
-import React = require('react');
-import _ = require('lodash');
interface DocumentLinksButtonProps {
View: DocumentView;
@@ -29,25 +29,43 @@ interface DocumentLinksButtonProps {
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 StartLinkView: DocumentView | undefined = undefined;
+ @observable public AnnotationId: string | undefined = undefined;
+ @observable public AnnotationUri: string | undefined = undefined;
+ @observable public LinkEditorDocView: DocumentView | undefined = undefined;
+ public static _instance: DocButtonState | undefined;
+ public static get Instance() {
+ return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
+ }
+ constructor() {
+ makeObservable(this);
+ }
+}
@observer
-export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
+export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> {
private _linkButton = React.createRef<HTMLDivElement>();
- @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
- @observable public static StartLinkView: DocumentView | undefined;
- @observable public static AnnotationId: string | undefined;
- @observable public static AnnotationUri: string | undefined;
- @observable public static LinkEditorDocView: DocumentView | undefined;
+ public static get StartLink() { return DocButtonState.Instance.StartLink; } // 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;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
- @action
@undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
- if (this.props.InMenu && this.props.StartLink) {
+ if (this._props.InMenu && this._props.StartLink) {
if (this._linkButton.current !== null) {
const linkDrag = UndoManager.StartBatch('Drag Link');
- this.props.View &&
- DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
+ this._props.View &&
+ DragManager.StartLinkDrag(this._linkButton.current, this._props.View, this._props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
dragComplete: dropEv => {
- if (this.props.View && dropEv.linkDocument) {
+ if (this._props.View && dropEv.linkDocument) {
// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
!dropEv.linkDocument.link_relationship && (Doc.GetProto(dropEv.linkDocument).link_relationship = 'hyperlink');
}
@@ -69,11 +87,11 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
this.onLinkButtonMoved,
emptyFunction,
action((e, doubleTap) => {
- doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc);
+ doubleTap && DocumentView.showBackLinks(this._props.View.Document);
}),
undefined,
undefined,
- action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
+ action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
);
};
@@ -84,33 +102,32 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
this.onLinkButtonMoved,
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu && this.props.StartLink) {
- //action(() => Doc.BrushDoc(this.props.View.Document));
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ if (doubleTap && this._props.InMenu && this._props.StartLink) {
+ //action(() => Doc.BrushDoc(this._props.View.Document));
+ if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
- DocumentLinksButton.StartLink = this.props.View.props.Document;
- DocumentLinksButton.StartLinkView = this.props.View;
+ DocumentLinksButton.StartLink = this._props.View.Document;
+ DocumentLinksButton.StartLinkView = this._props.View;
}
}
})
);
};
- @action
@undoBatch
onLinkClick = (e: React.MouseEvent): void => {
- if (this.props.InMenu && this.props.StartLink) {
+ if (this._props.InMenu && this._props.StartLink) {
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
//if this LinkButton's Document is undefined
- DocumentLinksButton.StartLink = this.props.View.props.Document;
- DocumentLinksButton.StartLinkView = this.props.View;
+ DocumentLinksButton.StartLink = this._props.View.Document;
+ DocumentLinksButton.StartLinkView = this._props.View;
}
}
};
@@ -121,7 +138,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
e,
returnFalse,
emptyFunction,
- action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View))
+ action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
);
};
@@ -133,7 +150,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- //!this.props.StartLink
+ //!this._props.StartLink
} else if (startLink !== endLink) {
endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
@@ -190,8 +207,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@computed get filteredLinks() {
const results = [] as Doc[];
- const filters = this.props.View.props.childFilters();
- Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
+ const filters = this._props.View._props.childFilters();
+ Array.from(new Set<Doc>(this._props.View.allLinks)).forEach(link => {
if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) {
results.push(link);
}
@@ -206,8 +223,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
*/
@computed get linkButtonInner() {
const btnDim = 30;
- const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink;
- const scaling = Math.min(1, this.props.scaling?.() || 1);
+ const isActive = DocumentLinksButton.StartLink === this._props.View.Document && this._props.StartLink;
+ const scaling = Math.min(1, this._props.scaling?.() || 1);
const showLinkCount = (onHover?: boolean, offset?: boolean) => (
<div
className="documentLinksButton-showCount"
@@ -221,16 +238,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
<span style={{ width: '100%', display: 'inline-block', textAlign: 'center' }}>{Array.from(this.filteredLinks).length}</span>
</div>
);
- return this.props.ShowCount ? (
- showLinkCount(this.props.OnHover, this.props.Bottom)
+ return this._props.ShowCount ? (
+ 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.props.Document ? ( //if the origin node is not this node
+ {!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>
@@ -240,21 +257,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
render() {
- if (this.props.hideCount?.()) return null;
- const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+ if (this._props.hideCount?.()) return null;
+ const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
const buttonTitle = 'Tap to view links; double tap to open link collection';
- const title = this.props.ShowCount ? buttonTitle : menuTitle;
+ 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
- return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : (
+ return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
<div
className="documentLinksButton-wrapper"
style={{
- position: this.props.InMenu ? 'relative' : 'absolute',
+ position: this._props.InMenu ? 'relative' : 'absolute',
top: 0,
pointerEvents: 'none',
}}>
- {DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
+ {DocButtonState.Instance.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
</div>
);
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 406a1b8fb..c4dab16fb 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.documentView-effectsWrapper {
border-radius: inherit;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0d6b88392..ab413e6f2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
-import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
+// import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
+import * as React from 'react';
+import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -15,12 +17,11 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from '../../DocServer';
-import { DocOptions, Docs, DocUtils, FInfo } from '../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DocOptions, DocUtils, Docs, FInfo } from '../../documents/Documents';
import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -32,13 +33,14 @@ import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProvider';
import { UndoStack } from '../UndoStack';
import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
@@ -46,12 +48,11 @@ import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
import { FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
import { PinProps, PresBox } from './trails/PresBox';
-import React = require('react');
const { Howl } = require('howler');
interface Window {
@@ -109,8 +110,10 @@ export interface DocFocusOptions {
easeFunc?: 'linear' | 'ease'; // transition method for scrolling
}
export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
export interface DocComponentView {
+ fieldKey?: string;
+ annotationKey?: string;
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;
@@ -119,10 +122,8 @@ export interface DocComponentView {
getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
- ignoreNativeDimScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
select?: (ctrlKey: boolean, shiftKey: boolean) => void;
focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
- menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
isAnyChildContentActive?: () => boolean; // is any child content of the document active
onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -136,11 +137,7 @@ export interface DocComponentView {
componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
incrementalRendering?: () => void;
- layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
- overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events)
- fieldKey?: string;
- annotationKey?: string;
- getTitle?: () => string;
+ infoUI?: () => JSX.Element | null;
getCenter?: (xf: Transform) => { X: number; Y: number };
screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
@@ -150,33 +147,28 @@ export interface DocComponentView {
}
// These props are passed to both FieldViews and DocumentViews
export interface DocumentViewSharedProps {
- fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
- DocumentView?: () => DocumentView;
renderDepth: number;
Document: Doc;
- DataDoc?: Doc;
+ TemplateDataDocument?: Doc;
+ scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+ DocumentView?: () => DocumentView;
+ CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
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
- suppressSetHeight?: boolean;
setContentView?: (view: DocComponentView) => any;
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
- shouldNotScale?: () => boolean;
docViewPath: () => DocumentView[];
- childHideDecorationTitle?: () => boolean;
- childHideResizeHandles?: () => boolean;
- childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+ childFilters: () => string[];
+ childFiltersByRanges: () => string[];
styleProvider: Opt<StyleProviderFunc>;
setTitleFocus?: () => void;
focus: DocFocusFunc;
layout_fitWidth?: (doc: Doc) => boolean | undefined;
- childFilters: () => string[];
- childFiltersByRanges: () => string[];
searchFilterDocs: () => Doc[];
layout_showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
- rootSelected: () => boolean; // whether the root of a template has been selected
+ rootSelected?: () => boolean; // whether the root of a template has been selected
addDocTab: (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;
@@ -185,26 +177,27 @@ export interface DocumentViewSharedProps {
pinToPres: (document: Doc, pinProps: PinProps) => void;
ScreenToLocalTransform: () => Transform;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- dragAction?: dropActionType;
+ waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+ defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
+ pointerEvents?: () => Opt<string>;
treeViewDoc?: Doc;
xPadding?: number;
yPadding?: number;
- dropAction?: dropActionType;
dontRegisterView?: boolean;
+ childHideDecorationTitle?: boolean;
+ childHideResizeHandles?: boolean;
+ childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+ dropAction?: dropActionType;
+ dragAction?: dropActionType;
dragWhenActive?: boolean;
+ dontHideOnDrag?: boolean;
hideLinkButton?: boolean;
hideCaptions?: boolean;
ignoreAutoHeight?: boolean;
forceAutoHeight?: boolean;
+ suppressSetHeight?: boolean;
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
- waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
- defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
- pointerEvents?: () => Opt<string>;
- scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
- createNewFilterDoc?: () => void;
- updateFilterDoc?: (doc: Doc) => void;
- dontHideOnDrag?: boolean;
}
// these props are specific to DocuentViews
@@ -218,12 +211,11 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
hideOpenButton?: boolean;
hideDeleteButton?: boolean;
hideLinkAnchors?: boolean;
- isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
- radialMenu?: String[];
LayoutTemplateString?: string;
dontCenter?: 'x' | 'y' | 'xy';
+ isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
NativeWidth?: () => number;
NativeHeight?: () => number;
NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
@@ -241,8 +233,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
// these props are only available in DocumentViewIntenral
export interface DocumentViewInternalProps extends DocumentViewProps {
- NativeWidth: () => number;
- NativeHeight: () => number;
isSelected: () => boolean;
select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
DocumentView: () => DocumentView;
@@ -264,6 +254,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _mainCont = React.createRef<HTMLDivElement>();
private _titleRef = React.createRef<EditableView>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
@observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
@observable _animateScaleTime: Opt<number>; // milliseconds for animating between views. defaults to 300 if not uset
@@ -273,7 +267,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this._animateScaleTime ?? 100;
}
public get displayName() {
- return 'DocumentViewInternal(' + this.props.Document.title + ')';
+ return 'DocumentViewInternal(' + this._props.Document.title + ')';
} // this makes mobx trace() statements more descriptive
public get ContentDiv() {
@@ -283,57 +277,51 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return Doc.LayoutFieldKey(this.layoutDoc);
}
@computed get layout_showTitle() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
}
@computed get NativeDimScaling() {
- return this.props.NativeDimScaling?.() || 1;
+ return this._props.NativeDimScaling?.() || 1;
}
@computed get thumb() {
return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
}
@computed get opacity() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Opacity);
}
@computed get boxShadow() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow);
}
@computed get borderRounding() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
}
@computed get widgetDecorations() {
TraceMobx();
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
}
@computed get backgroundBoxColor() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
}
@computed get docContents() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
}
@computed get headerMargin() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
}
@computed get layout_showCaption() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowCaption) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
}
@computed get titleHeight() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
}
- @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
+ @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
@computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
return this._pointerEvents;
}
@computed get finalLayoutKey() {
return StrCast(this.Document.layout_fieldKey, 'layout');
}
- @computed get nativeWidth() {
- return this.props.NativeWidth();
- }
- @computed get nativeHeight() {
- return this.props.NativeHeight();
- }
@computed get disableClickScriptFunc() {
- const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
+ const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
// prettier-ignore
return (
DocumentView.LongPress ||
@@ -342,49 +330,49 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
);
}
@computed get onClickHandler() {
- return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+ return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
}
@computed get onDoubleClickHandler() {
- return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+ return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
}
@computed get onPointerDownHandler() {
- return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+ return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
}
@computed get onPointerUpHandler() {
- return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+ return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
}
componentWillUnmount() {
this.cleanupHandlers(true);
}
@observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
- @action
+
componentDidMount() {
- this._mounted = true;
+ runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
() => {
// true - if the document has been activated directly or indirectly (by having its children selected)
// false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
// undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
- return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
+ return this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none'
? false
- : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
- ? true
- : undefined;
+ : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
+ ? true
+ : undefined;
},
active => (this._isContentActive = active),
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
- () => this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents),
+ () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
pointerevents => (this._pointerEvents = pointerevents),
{ fireImmediately: true }
);
}
preDropFunc = (e: Event, de: DragManager.DropEvent) => {
const dropAction = this.layoutDoc.dropAction as dropActionType;
- if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) {
+ if (de.complete.docDragData && this.isContentActive() && !this._props.treeViewDoc) {
dropAction && (de.complete.docDragData.dropAction = dropAction);
e.stopPropagation();
}
@@ -392,46 +380,46 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
setupHandlers() {
this.cleanupHandlers(false);
if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
}
}
- @action
+
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
- unbrush && Doc.UnBrushDoc(this.props.Document);
+ unbrush && Doc.UnBrushDoc(this._props.Document);
Object.values(this._disposers).forEach(disposer => disposer?.());
}
startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
if (this._mainCont.current) {
- const views = SelectionManager.Views().filter(dv => dv.docView?._mainCont.current);
- const selected = views.some(dv => dv.rootDoc === this.Document) ? views : [this.props.DocumentView()];
- const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.rootDoc));
- const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
- dragData.offset = this.props
+ const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
+ const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
+ const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
+ const [left, top] = this._props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
+ dragData.offset = this._props
.ScreenToLocalTransform()
.scale(this.NativeDimScaling)
.transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
- dragData.treeViewDoc = this.props.treeViewDoc;
- dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;
- dragData.draggedViews = [this.props.DocumentView()];
- dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false;
+ dragData.treeViewDoc = this._props.treeViewDoc;
+ dragData.removeDocument = this._props.removeDocument;
+ dragData.moveDocument = this._props.moveDocument;
+ dragData.draggedViews = [this._props.DocumentView()];
+ dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
DragManager.StartDocumentDrag(
selected.map(dv => dv.docView!._mainCont.current!),
dragData,
x,
y,
- { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }
+ { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this._props.dontHideOnDrag) }
); // this needs to happen after the drop event is processed.
}
}
defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
const targetMatch =
- Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document
- (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
+ Doc.AreProtosEqual(anchor, this.Document) || // anchor is this document, so anchor's properties apply to this document
+ (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.Document)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
? true
: false;
return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
@@ -446,29 +434,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
- if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
+ if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
let preventDefault = true;
- !this.rootDoc._keepZWhenDragged && this.props.bringToFront(this.rootDoc);
+ !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
if (this._doubleTap) {
- const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
+ const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
if (this.onDoubleClickHandler?.script) {
const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
// prettier-ignore
const func = () => this.onDoubleClickHandler.script.run( {
- this: this.layoutDoc,
- self: this.rootDoc,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView(),
+ this: this.Document,
+ scriptContext: this._props.scriptContext,
+ documentView: this._props.DocumentView(),
clientX, clientY, altKey, shiftKey, ctrlKey,
value: undefined,
}, console.log );
- UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
- } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
- UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
+ UndoManager.RunInBatch(() => (func().result?.select === true ? this._props.select(false) : ''), 'on double click');
+ } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
+ UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
+ Doc.UnBrushDoc(this._props.Document);
} else {
this._singleClickFunc?.();
}
@@ -485,14 +472,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
// instead of in the global lightbox
const oldFunc = DocumentViewInternal.addDocTabFunc;
- DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
+ DocumentViewInternal.addDocTabFunc = this._props.addDocTab;
this.onClickHandler?.script.run(
{
- this: this.layoutDoc,
- self: this.rootDoc,
+ this: this.Document,
_readOnly_: false,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView(),
+ scriptContext: this._props.scriptContext,
+ documentView: this._props.DocumentView(),
clientX,
clientY,
shiftKey,
@@ -501,14 +487,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
},
console.log
).result?.select === true
- ? this.props.select(false)
+ ? this._props.select(false)
: '';
DocumentViewInternal.addDocTabFunc = oldFunc;
};
- clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title);
+ clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.Document.title);
} else {
// onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
- if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
+ if ((this.layoutDoc.onDragStart || this._props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
}
preventDefault = false;
@@ -516,10 +502,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const sendToBack = e.altKey;
this._singleClickFunc =
// prettier-ignore
- clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.rootDoc, true) :
+ clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) :
this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ??
- this.props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
- const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
+ this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
+ const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
@@ -533,40 +519,39 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
});
- @action
onPointerDown = (e: React.PointerEvent): void => {
- if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
+ if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
this._longPressSelector = setTimeout(() => {
if (DocumentView.LongPress) {
- if (this.rootDoc.undoIgnoreFields) {
+ if (this.Document.undoIgnoreFields) {
runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
} else {
- this.props.select(false);
+ this._props.select(false);
}
}
}, 1000);
- if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
+ if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
this._downX = e.clientX;
this._downY = e.clientY;
this._downTime = Date.now();
- if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
+ if ((Doc.ActiveTool === InkTool.None || this._props.addDocTab === returnFalse) && !(this._props.TemplateDataDocument && !(e.ctrlKey || e.button > 0))) {
// click events stop here if the document is active and no modes are overriding it
// if this is part of a template, let the event go up to the template root unless right/ctrl clicking
if (
// prettier-ignore
- (this.props.isDocumentActive?.() || this.props.isContentActive?.()) &&
- !this.props.onBrowseClick?.() &&
+ (this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
+ !this._props.onBrowseClick?.() &&
!this.Document.ignoreClick &&
e.button === 0 &&
!Doc.IsInMyOverlay(this.layoutDoc)
) {
e.stopPropagation();
// don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
- //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
+ //if (this._props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
// listen to move events if document content isn't active or document is draggable
- if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive, this.props.dragWhenActive))) {
+ if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) {
document.addEventListener('pointermove', this.onPointerMove);
}
}
@@ -574,14 +559,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
};
- @action
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())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
}
};
@@ -590,31 +574,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
document.removeEventListener('pointerup', this.onPointerUp);
};
- @action
onPointerUp = (e: PointerEvent): void => {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
if (this.onPointerUpHandler?.script) {
- this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
+ 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.rootDoc.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+ this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
}
if (DocumentView.LongPress) e.preventDefault();
};
@undoBatch
- @action
toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
- const hadOnClick = this.rootDoc.onClick;
+ const hadOnClick = this.Document.onClick;
this.noOnClick();
this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
};
@undoBatch
- @action
- followLinkOnClick = (): void => {
+ followLinkOnClick = () => {
this.Document.ignoreClick = false;
this.Document.onClick = FollowLinkScript();
this.Document.followLinkToggle = false;
@@ -622,12 +603,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.Document.followLinkLocation = undefined;
};
@undoBatch
- noOnClick = (): void => {
+ noOnClick = () => {
this.Document.ignoreClick = false;
this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
};
- @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
+ @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
@undoBatch setToggleDetail = () =>
(this.Document.onClick = ScriptField.MakeScript(
`toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
@@ -637,10 +618,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
));
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
- if (this.props.Document === Doc.ActiveDashboard) {
+ if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
+ if (this._props.Document === Doc.ActiveDashboard) {
e.stopPropagation();
e.preventDefault();
alert(
@@ -653,16 +633,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
if (linkdrag) {
linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
- if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.rootDoc) {
+ if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.Document) {
if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
}
- if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.embedContainer) {
- const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
+ if (de.complete.annoDragData || this.Document !== linkdrag.linkSourceDoc.embedContainer) {
+ const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document;
de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
if (de.complete.linkDocument) {
de.complete.linkDocument.layout_isSvg = true;
- this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+ this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
}
}
e.stopPropagation();
@@ -673,18 +653,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
@undoBatch
- @action
makeIntoPortal = () => {
- const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.props.Document && d.link_relationship === 'portal to:portal from');
+ const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
if (!portalLink) {
DocUtils.MakeLink(
- this.props.Document,
+ this._props.Document,
Docs.Create.FreeformDocument([], {
_width: NumCast(this.layoutDoc._width) + 10,
_height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
_isLightbox: true,
_layout_fitWidth: true,
- title: StrCast(this.props.Document.title) + ' [Portal]',
+ title: StrCast(this._props.Document.title) + ' [Portal]',
}),
{ link_relationship: 'portal to:portal from' }
);
@@ -702,7 +681,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const batch = UndoManager.StartBatch('importing');
Doc.importDocument(input.files[0]).then(doc => {
if (doc instanceof Doc) {
- this.props.addDocTab(doc, OpenWhere.addRight);
+ this._props.addDocTab(doc, OpenWhere.addRight);
batch.end();
}
});
@@ -711,12 +690,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
input.click();
};
- @action
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e && this.rootDoc._layout_hideContextMenu && Doc.noviceMode) {
+ if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
- //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -738,7 +716,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && !(e.nativeEvent as any).dash) {
const onDisplay = () => {
- if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected() && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this._props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
};
if (navigator.userAgent.includes('Macintosh')) {
@@ -749,38 +727,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return;
}
- const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
+ const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: 'sticky-note' })
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
- this.props
- .contextMenuItems?.()
- .forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
+ this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
- if (!this.props.Document.isFolder) {
- const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null);
+ if (!this._props.Document.isFolder) {
+ const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
const appearance = cm.findByDescription('Appearance...');
const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
- if (this.props.renderDepth === 0) {
- appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.rootDoc), icon: 'external-link-alt' });
+ if (this._props.renderDepth === 0) {
+ appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
}
- this.rootDoc.type === DocumentType.PRES && appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.rootDoc, {}), icon: 'eye' });
- !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
+ appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+ !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
- if (!Doc.IsSystem(this.rootDoc) && this.rootDoc.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.rootDoc._type_collection as any)) {
+ if (!Doc.IsSystem(this.Document) && this.Document.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._type_collection as any)) {
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- if (this.props.bringToFront !== emptyFunction) {
+ if (this._props.bringToFront !== emptyFunction) {
const zorders = cm.findByDescription('ZOrder...');
const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
- zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: 'arrow-up' });
- zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: 'arrow-down' });
+ zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
+ zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
zorderItems.push({
- description: !this.rootDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
- event: undoBatch(action(() => (this.rootDoc._keepZWhenDragged = !this.rootDoc._keepZWhenDragged))),
+ description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
+ 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' });
@@ -789,14 +765,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
!Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
- if (!this.props.treeViewDoc) {
+ if (!this._props.treeViewDoc) {
if (!this.Document.annotationOn) {
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
- !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+ !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this._props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
!existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
@@ -816,43 +792,43 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const more = cm.findByDescription('More...');
const moreItems = more && 'subitems' in more ? more.subitems : [];
- if (!Doc.IsSystem(this.rootDoc)) {
+ if (!Doc.IsSystem(this.Document)) {
if (!Doc.noviceMode) {
- moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: 'concierge-bell' });
+ moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.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' });
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: 'caret-square-right' });
- moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: 'caret-square-right' });
- moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: 'caret-square-right' });
+ if (Cast(Doc.GetProto(this._props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this._props.Document }).then(console.log), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this._props.Document), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this._props.Document), icon: 'caret-square-right' });
}
- moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
+ moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
}
}
!more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
}
const constantItems: ContextMenuProps[] = [];
- if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking) {
- constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
- (this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
- if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
+ if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
+ (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
+ if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
}
- constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+ constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
- !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
- !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
- !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DocData]), icon: 'hand-point-right' });
+ !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this._props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
+ !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;
- switch (this.props.Document.type) {
+ switch (this._props.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -887,7 +863,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
break;
}
// Add link to help documentation
- if (!this.props.treeViewDoc && documentationDescription && documentationLink) {
+ if (!this._props.treeViewDoc && documentationDescription && documentationLink) {
helpItems.push({
description: documentationDescription,
event: () => window.open(documentationLink, '_blank'),
@@ -902,26 +878,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
@computed get _rootSelected() {
- return this.props.isSelected() || (this.props.Document.rootDocument && this.props.rootSelected?.()) || false;
+ return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
}
rootSelected = () => this._rootSelected;
- panelHeight = () => this.props.PanelHeight() - this.headerMargin;
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ panelHeight = () => this._props.PanelHeight() - this.headerMargin;
+ screenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
- setHeight = (height: number) => (this.layoutDoc._height = height);
+ setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
- @observable _isContentActive: boolean | undefined;
+ @observable _isContentActive: boolean | undefined = undefined;
isContentActive = (): boolean | undefined => this._isContentActive;
- childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
+ childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
/// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
@computed get _contentPointerEvents() {
TraceMobx();
- return this.props.contentPointerEvents ??
+ return this._props.contentPointerEvents ??
((!this.disableClickScriptFunc && //
this.onClickHandler &&
- !this.props.onBrowseClick?.() &&
+ !this._props.onBrowseClick?.() &&
this.isContentActive() !== true) ||
this.isContentActive() === false)
? 'none'
@@ -930,8 +906,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
contentPointerEvents = () => this._contentPointerEvents;
@computed get contents() {
TraceMobx();
- const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
- const noBackground = this.rootDoc._isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
+ const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+ const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return (
<div
className="documentView-contentsView"
@@ -941,19 +917,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}}>
<DocumentContentsView
key={1}
- {...this.props}
+ {...this._props}
+ fieldKey=""
pointerEvents={this.contentPointerEvents}
- docViewPath={this.props.viewPath}
+ docViewPath={this._props.viewPath}
setContentView={this.setContentView}
childFilters={this.childFilters}
- NativeDimScaling={this.props.NativeDimScaling}
PanelHeight={this.panelHeight}
- setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
+ setHeight={this.setHeight}
isContentActive={this.isContentActive}
ScreenToLocalTransform={this.screenToLocal}
rootSelected={this.rootSelected}
onClick={this.onClickFunc}
- focus={this.props.focus}
setTitleFocus={this.setTitleFocus}
layout_fieldKey={this.finalLayoutKey}
/>
@@ -962,8 +937,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
);
}
- anchorPanelWidth = () => this.props.PanelWidth() || 1;
- anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ anchorPanelWidth = () => this._props.PanelWidth() || 1;
+ anchorPanelHeight = () => this._props.PanelHeight() || 1;
anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
// prettier-ignore
switch (property.split(':')[0]) {
@@ -971,11 +946,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
case StyleProp.PointerEvents: return 'none';
case StyleProp.Highlighting: return undefined;
case StyleProp.Opacity: {
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ 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;
}
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
// We need to use allrelatedLinks to get not just links to the document as a whole, but links to
// anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document). e.g.,
@@ -985,31 +960,31 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// For each of these, we create LinkAnchorBox's on the border of the DocumentView.
@computed get directLinks() {
TraceMobx();
- return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(
+ return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
link =>
- (link.link_matchEmbeddings ? link.link_anchor_1 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc)) ||
- (link.link_matchEmbeddings ? link.link_anchor_2 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc)) ||
- ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) ||
- ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc))
+ (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
+ (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
+ ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
+ ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
);
}
@computed get allLinks() {
TraceMobx();
- return LinkManager.Instance.getAllRelatedLinks(this.rootDoc);
+ return LinkManager.Instance.getAllRelatedLinks(this.Document);
}
hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
@computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
TraceMobx();
- if (this._componentView instanceof KeyValueBox || this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
+ const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.map(link => (
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView
- {...this.props}
+ {...this._props}
isContentActive={returnFalse}
Document={link}
- docViewPath={this.props.viewPath}
+ docViewPath={this._props.viewPath}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
@@ -1020,7 +995,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
removeDocument={this.hideLink(link)}
styleProvider={this.anchorStyleProvider}
LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
/>
</div>
));
@@ -1099,7 +1074,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
};
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
@observable _changingTitleField = false;
@observable _dropDownInnerWidth = 0;
fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1143,12 +1118,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
<div
className="documentView-captionWrapper"
style={{
- pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
}}>
<FormattedTextBox
- {...this.props}
+ {...this._props}
yPadding={10}
xPadding={10}
fieldKey={this.layout_showCaption}
@@ -1156,12 +1131,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
dontRegisterView={true}
noSidebar={true}
dontScale={true}
- renderDepth={this.props.renderDepth}
+ renderDepth={this._props.renderDepth}
isContentActive={this.isContentActive}
/>
</div>
);
- const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
+ 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))
@@ -1182,7 +1157,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
width: 100 - sidebarWidthPercent + '%',
color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
background,
- pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
{!dropdownWidth
? null
@@ -1191,9 +1166,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
dropdownWidth,
StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
action((field: string | number) => {
- if (this.rootDoc.layout_showTitle) {
- this.rootDoc._layout_showTitle = field;
- } else if (!this.props.layout_showTitle) {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
Doc.UserDoc().layout_showTitle = field;
}
this._changingTitleField = false;
@@ -1216,12 +1191,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
- GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.rootDoc, showTitle.split(';')[0]))}
+ GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
SetValue={undoBatch((input: string) => {
if (input?.startsWith('#')) {
- if (this.rootDoc.layout_showTitle) {
- this.rootDoc._layout_showTitle = input?.substring(1);
- } else if (!this.props.layout_showTitle) {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = input?.substring(1);
+ } else if (!this._props.layout_showTitle) {
Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
}
} else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
@@ -1233,7 +1208,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>
</div>
);
- return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+ return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
this.contents
) : (
<div className="documentView-styleWrapper">
@@ -1246,7 +1221,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
renderDoc = (style: object) => {
TraceMobx();
- return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DocData]) === AclPrivate
+ return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate
? null
: this.docContents ?? (
<div
@@ -1289,31 +1264,31 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
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>;
- case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
- case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
- case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
- case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
+ // case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+ // case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
+ // case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
+ // case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+ // case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+ // case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
+ // case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
}
}
@computed get highlighting() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
}
@computed get borderPath() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BorderPath);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
}
render() {
TraceMobx();
const highlighting = this.highlighting;
const borderPath = this.borderPath;
const boxShadow =
- this.props.treeViewDoc || !highlighting
+ this._props.treeViewDoc || !highlighting
? this.boxShadow
: highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
- ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
- : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+ ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+ : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
const renderDoc = this.renderDoc({
borderRadius: this.borderRounding,
outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1329,15 +1304,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
- onPointerOver={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
- onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
+ onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+ onPointerOver={e => (!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.rootDoc[Animation], this.rootDoc)}
+ {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
{borderPath?.jsx}
</>
</div>
@@ -1346,14 +1321,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
@observer
-export class DocumentView extends React.Component<DocumentViewProps> {
+export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
public static ROOT_DIV = 'documentView-effectsWrapper';
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable _selected = false;
public get SELECTED() {
return this._selected;
}
public set SELECTED(val) {
- this._selected = val;
+ runInAction(() => (this._selected = val));
}
@observable public static Interacting = false;
@observable public static LongPress = false;
@@ -1362,14 +1343,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed public static get exploreMode() {
return () => (DocumentView.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
- @observable public docView: DocumentViewInternal | undefined | null;
+ @observable public docView: DocumentViewInternal | undefined | null = undefined;
@observable public textHtmlOverlay: Opt<string>;
@observable public textHtmlOverlayTime: Opt<number>;
@observable private _isHovering = false;
public htmlOverlayEffect: Opt<Doc>;
public get displayName() {
- return 'DocumentView(' + this.props.Document?.title + ')';
+ return 'DocumentView(' + this._props.Document?.title + ')';
} // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
public ViewTimer: NodeJS.Timeout | undefined; // timer for res
@@ -1377,7 +1358,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
private _disposers: { [name: string]: IReactionDisposer } = {};
public clearViewTransition = () => {
this.ViewTimer && clearTimeout(this.ViewTimer);
- this.rootDoc._viewTransition = undefined;
+ this.layoutDoc._viewTransition = undefined;
};
public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
@@ -1385,16 +1366,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
- this.rootDoc[Animation] = presEffect;
- this.AnimEffectTimer = setTimeout(() => (this.rootDoc[Animation] = undefined), timeInMs);
+ this.Document[Animation] = presEffect;
+ this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
};
public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
- this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`;
- if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`;
+ this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
+ if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`;
this.ViewTimer && clearTimeout(this.ViewTimer);
return (this.ViewTimer = setTimeout(() => {
- this.rootDoc._viewTransition = undefined;
- this.rootDoc._dataTransition = 'inherit';
+ this.layoutDoc._viewTransition = undefined;
+ this.Document._dataTransition = 'inherit';
afterTrans?.();
}, timeInMs + 10));
};
@@ -1421,19 +1402,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
DocServer.GetRefField(docId).then(docx =>
LightboxView.Instance.SetLightboxDoc(
(docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
- Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self, self.target)') }, docId)
+ Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
)
);
}
get Document() {
- return this.props.Document;
+ return this._props.Document;
}
get topMost() {
- return this.props.renderDepth === 0;
- }
- get rootDoc() {
- return this.docView?.rootDoc ?? this.Document;
+ return this._props.renderDepth === 0;
}
get dataDoc() {
return this.docView?.dataDoc ?? this.Document;
@@ -1445,38 +1423,38 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?._componentView;
}
get allLinks() {
- return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.rootDoc || link.link_anchor_2 === this.rootDoc);
+ return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
}
get LayoutFieldKey() {
return this.docView?.LayoutFieldKey || 'layout';
}
@computed get layout_fitWidth() {
- return this.docView?._componentView?.layout_fitWidth?.() ?? this.props.layout_fitWidth?.(this.rootDoc) ?? this.layoutDoc?.layout_fitWidth;
+ return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
}
@computed get anchorViewDoc() {
- return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.rootDoc['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.rootDoc['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 hideLinkButton() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : ''));
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
}
- hideLinkCount = () => this.props.renderDepth === -1 || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
+ hideLinkCount = () => this._props.renderDepth === -1 || (this.SELECTED && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
@computed get linkCountView() {
return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
}
@computed get docViewPath(): DocumentView[] {
- return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
+ return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
}
@computed get layoutDoc() {
- return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
+ return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
}
@computed get nativeWidth() {
- return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth));
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed get nativeHeight() {
- return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth));
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed get shouldNotScale() {
- return this.props.shouldNotScale?.() || (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
+ return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
}
@computed get effectiveNativeWidth() {
return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1487,57 +1465,57 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed get nativeScaling() {
if (this.shouldNotScale) return 1;
const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
- if (this.layout_fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
+ if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+ return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
}
- return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
}
@computed get panelWidth() {
- return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
+ return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
}
@computed get panelHeight() {
if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
- return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
+ return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
}
- return this.props.PanelHeight();
+ return this._props.PanelHeight();
}
@computed get Xshift() {
- return this.effectiveNativeWidth ? Math.max(0, (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
+ return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
}
@computed get Yshift() {
return this.effectiveNativeWidth &&
this.effectiveNativeHeight &&
Math.abs(this.Xshift) < 0.001 &&
- (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
- ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
+ (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this._props.PanelHeight()))
+ ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
: 0;
}
@computed get centeringX() {
- return this.props.dontCenter?.includes('x') ? 0 : this.Xshift;
+ return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
}
@computed get centeringY() {
- return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
+ return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
}
@computed get CollectionFreeFormView() {
return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
}
@computed get CollectionFreeFormDocumentView() {
- return this.props.CollectionFreeFormDocumentView?.();
+ return this._props.CollectionFreeFormDocumentView?.();
}
- public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
+ public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
public getBounds = () => {
- if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+ if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
return undefined;
}
if (this.docView._componentView?.screenBounds?.()) {
return this.docView._componentView.screenBounds();
}
- const xf = this.docView.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+ const xf = this.docView._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
- if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
}
@@ -1560,22 +1538,21 @@ export class DocumentView extends React.Component<DocumentViewProps> {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
this.Document.deiconifyLayout = undefined;
- this.props.bringToFront(this.rootDoc);
+ this._props.bringToFront(this.Document);
}
}
@undoBatch
- @action
setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this.props.Document);
- custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ Doc.setNativeView(this._props.Document);
+ custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
};
- @action
+
switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
- this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
setTimeout(
action(() => {
- if (useExistingLayout && custom && this.rootDoc['layout_' + view]) {
- this.rootDoc.layout_fieldKey = 'layout_' + view;
+ if (useExistingLayout && custom && this.Document['layout_' + view]) {
+ this.Document.layout_fieldKey = 'layout_' + view;
} else {
this.setCustomView(custom, view);
}
@@ -1592,15 +1569,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
);
};
- scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
+ layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
+ scaleToScreenSpace = () => (1 / (this._props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
docViewPathFunc = () => this.docViewPath;
isSelected = () => this.SELECTED;
select = (extendSelection: boolean, focusSelection?: boolean) => {
- if (this.isSelected() && SelectionManager.Views().length > 1) SelectionManager.DeselectView(this);
+ if (this.SELECTED && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
else {
SelectionManager.SelectView(this, extendSelection);
if (focusSelection) {
- DocumentManager.Instance.showDocument(this.rootDoc, {
+ DocumentManager.Instance.showDocument(this.Document, {
willZoomCentered: true,
zoomScale: 0.9,
zoomTime: 500,
@@ -1608,6 +1586,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
};
+ ShouldNotScale = () => this.shouldNotScale;
NativeWidth = () => this.effectiveNativeWidth;
NativeHeight = () => this.effectiveNativeHeight;
PanelWidth = () => this.panelWidth;
@@ -1615,15 +1594,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
NativeDimScaling = () => this.nativeScaling;
selfView = () => this;
screenToLocalTransform = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
.translate(-this.centeringX, -this.centeringY)
.scale(1 / this.nativeScaling);
- @action
componentDidMount() {
- this.rootDoc[DocViews].add(this);
- this._disposers.updateContentsScript = reaction(() => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.rootDoc, self: this.rootDoc }).result, emptyFunction);
+ runInAction(() => this.Document[DocViews].add(this));
+ this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
this._disposers.height = reaction(
// increase max auto height if document has been resized to be greater than current max
() => NumCast(this.layoutDoc._height),
@@ -1632,13 +1610,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
})
);
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
}
- @action
+
componentWillUnmount() {
- this.rootDoc[DocViews].delete(this);
+ this.Document[DocViews].delete(this);
Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+ !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
}
// want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
// unfortunately, CSS can't transition animate any properties for something that is display 'none'.
@@ -1662,34 +1640,36 @@ export class DocumentView extends React.Component<DocumentViewProps> {
<ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
</div>,
{ ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
- this.rootDoc
+ this.Document
)}
</div>
</div>
);
}
+ @computed get infoUI() {
+ return this.ComponentView?.infoUI?.();
+ }
+
render() {
TraceMobx();
- const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
- const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+ const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
+ const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
<div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
- {!this.props.Document || !this.props.PanelWidth() ? null : (
+ {!this._props.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
ref={this.ContentRef}
style={{
- transition: 'inherit', // this.props.dataTransition,
+ transition: 'inherit', // this._props.dataTransition,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
- height: this.props.forceAutoHeight
- ? undefined
- : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+ width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
+ height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
}}>
<DocumentViewInternal
- {...this.props}
+ {...this._props}
DocumentView={this.selfView}
viewPath={this.docViewPathFunc}
PanelWidth={this.PanelWidth}
@@ -1699,11 +1679,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
NativeDimScaling={this.NativeDimScaling}
isSelected={this.isSelected}
select={this.select}
+ layout_fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToLocalTransform}
- focus={this.props.focus || emptyFunction}
+ focus={this._props.focus || emptyFunction}
ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
/>
{this.htmlOverlay}
+ {this.infoUI}
</div>
)}
@@ -1719,7 +1701,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
});
ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
- LightboxView.Instance.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0);
+ LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
});
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index f5871db22..5009ec7a7 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.equationBox-cont {
transform-origin: center;
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index d347c285b..ff92c701f 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,5 +1,4 @@
-import EquationEditor from 'equation-editor-react';
-import { action, reaction } from 'mobx';
+import { action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Id } from '../../../fields/FieldSymbols';
@@ -11,6 +10,7 @@ import { ViewBoxBaseComponent } from '../DocComponent';
import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
+import EquationEditor from './formattedText/EquationEditor';
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -19,13 +19,19 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
public static SelectOnLoad: string = '';
_ref: React.RefObject<EquationEditor> = React.createRef();
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
- this.props.setContentView?.(this);
- if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
- this.props.select(false);
+ this._props.setContentView?.(this);
+ if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+ this._props.select(false);
this._ref.current!.mathField.focus();
- this.rootDoc.text === 'x' && this._ref.current!.mathField.select();
+ this.dataDoc.text === 'x' && this._ref.current!.mathField.select();
EquationBox.SelectOnLoad = '';
}
reaction(
@@ -38,7 +44,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
//{ fireImmediately: true }
);
reaction(
- () => this.props.isSelected(),
+ () => this._props.isSelected(),
selected => {
if (this._ref.current) {
if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true);
@@ -62,21 +68,21 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
y: NumCast(this.layoutDoc.y) + _height + 10,
});
EquationBox.SelectOnLoad = nextEq[Id];
- this.props.addDocument?.(nextEq);
+ this._props.addDocument?.(nextEq);
e.stopPropagation();
}
if (e.key === 'Tab') {
- const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
+ const graph = Docs.Create.FunctionPlotDocument([this.Document], {
x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width),
y: NumCast(this.layoutDoc.y),
_width: 400,
_height: 300,
backgroundColor: 'white',
});
- this.props.addDocument?.(graph);
+ this._props.addDocument?.(graph);
e.stopPropagation();
}
- if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
+ if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
};
@undoBatch
onChange = (str: string) => (this.dataDoc.text = str);
@@ -100,7 +106,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
return (
<div
ref={r => this.updateSize()}
@@ -110,8 +116,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
transform: `scale(${scale})`,
width: 'fit-content', // `${100 / scale}%`,
height: `${100 / scale}%`,
- pointerEvents: !this.props.isSelected() ? 'none' : undefined,
- fontSize: StrCast(this.rootDoc._text_fontSize),
+ pointerEvents: !this._props.isSelected() ? 'none' : undefined,
+ 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" />
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 20afa4565..8d03bf57a 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -1,14 +1,14 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { observable, runInAction } from "mobx";
-import { RectangleTemplate } from "./FaceRectangles";
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, runInAction } from 'mobx';
+import { RectangleTemplate } from './FaceRectangles';
@observer
export default class FaceRectangle extends React.Component<{ rectangle: RectangleTemplate }> {
@observable private opacity = 0;
componentDidMount() {
- setTimeout(() => runInAction(() => this.opacity = 1), 500);
+ setTimeout(() => runInAction(() => (this.opacity = 1)), 500);
}
render() {
@@ -18,12 +18,11 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
style={{
...rectangle.style,
opacity: this.opacity,
- transition: "1s ease opacity",
- position: "absolute",
- borderRadius: 5
+ transition: '1s ease opacity',
+ position: 'absolute',
+ borderRadius: 5,
}}
/>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 0d1e063af..26e720c0d 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -1,9 +1,9 @@
-import React = require("react");
-import { Doc, DocListCast } from "../../../fields/Doc";
-import { Cast, NumCast } from "../../../fields/Types";
-import { observer } from "mobx-react";
-import { Id } from "../../../fields/FieldSymbols";
-import FaceRectangle from "./FaceRectangle";
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Cast, NumCast } from '../../../fields/Types';
+import { observer } from 'mobx-react';
+import { Id } from '../../../fields/FieldSymbols';
+import FaceRectangle from './FaceRectangle';
interface FaceRectanglesProps {
document: Doc;
@@ -18,7 +18,6 @@ export interface RectangleTemplate {
@observer
export class FaceRectangles extends React.Component<FaceRectanglesProps> {
-
render() {
const faces = DocListCast(this.props.document.faces);
const templates: RectangleTemplate[] = faces.map(faceDoc => {
@@ -33,14 +32,15 @@ export class FaceRectangles extends React.Component<FaceRectanglesProps> {
} as React.CSSProperties;
return {
id: rectangle[Id],
- style: style
+ style: style,
};
});
return (
<div>
- {templates.map(rectangle => <FaceRectangle key={rectangle.id} rectangle={rectangle} />)}
+ {templates.map(rectangle => (
+ <FaceRectangle key={rectangle.id} rectangle={rectangle} />
+ ))}
</div>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f7f94c546..f4c5167a5 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import { DateField } from '../../../fields/DateField';
@@ -16,7 +16,6 @@ import { DocumentViewSharedProps } from './DocumentView';
export interface FieldViewProps extends DocumentViewSharedProps {
// FieldView specific props that are not part of DocumentView props
fieldKey: string;
- scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further
select: (isCtrlPressed: boolean) => void;
isContentActive: (outsideReaction?: boolean) => boolean | undefined;
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 0aa2ac8e1..1c034bfbe 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,12 +1,12 @@
-import { Doc } from "../../../../fields/Doc";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { ButtonType } from "./FontIconBox";
+import { Doc } from '../../../../fields/Doc';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { ButtonType } from './FontIconBox';
export interface IButtonProps {
type: string | ButtonType;
- rootDoc: Doc;
+ Document: Doc;
label: any;
icon: IconProp;
color: string;
backgroundColor: string;
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index 9d9fa26b0..db2ffa756 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
.menuButton {
height: 100%;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index de5ad1631..5a8665aaf 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,7 +1,7 @@
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 { action, computed, observable } from 'mobx';
+import { computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
@@ -13,7 +13,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { undoable, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
-import { DocComponent } from '../../DocComponent';
+import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { SelectedDocView } from '../../selectedDoc';
import { StyleProp } from '../../StyleProvider';
@@ -26,7 +26,6 @@ export enum ButtonType {
TextButton = 'textBtn',
MenuButton = 'menuBtn',
DropdownList = 'dropdownList',
- DropdownButton = 'dropdownBtn',
ClickButton = 'clickBtn',
ToggleButton = 'toggleBtn',
ColorButton = 'colorBtn',
@@ -42,10 +41,15 @@ export interface ButtonProps extends FieldViewProps {
type?: ButtonType;
}
@observer
-export class FontIconBox extends DocComponent<ButtonProps>() {
+export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(FontIconBox, fieldKey);
}
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
//
// This controls whether fontIconButtons will display labels under their icons or not
//
@@ -55,10 +59,11 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
public static set ShowIconLabels(show: boolean) {
Doc.UserDoc()._showLabel = show;
}
+
@observable noTooltip = false;
showTemplate = (): void => {
const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
+ dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight);
};
dragAsTemplate = (): void => {
this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
@@ -78,7 +83,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
// Determining UI Specs
@computed get label() {
- return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
+ return StrCast(this.dataDoc.icon_label, StrCast(this.Document.title));
}
Icon = (color: string, iconFalse?: boolean) => {
let icon;
@@ -91,13 +96,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
};
@computed get dropdown() {
- return BoolCast(this.rootDoc.dropDownOpen);
+ return BoolCast(this.Document.dropDownOpen);
}
@computed get buttonList() {
- return StrListCast(this.rootDoc.btnList);
+ return StrListCast(this.Document.btnList);
}
@computed get type() {
- return StrCast(this.rootDoc.btnType);
+ return StrCast(this.Document.btnType);
}
/**
@@ -131,8 +136,8 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
type = 'slider';
break;
}
- const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined });
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ 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
const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
@@ -144,52 +149,21 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
showPlusMinus={false}
tooltip={this.label}
type={Type.PRIM}
- min={NumCast(this.rootDoc.numBtnMin, 0)}
- max={NumCast(this.rootDoc.numBtnMax, 100)}
+ min={NumCast(this.dataDoc.numBtnMin, 0)}
+ max={NumCast(this.dataDoc.numBtnMax, 100)}
number={checkResult}
- setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}
+ setNumber={undoable(value => numScript(value), `${this.Document.title} button set from list`)}
fillWidth
/>
);
}
- /**
- * Dropdown button
- */
- @computed get dropdownButton() {
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- return (
- <div
- className={`menuButton ${this.type} ${active}`}
- style={{ color, backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => {
- this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
- this.noTooltip = this.rootDoc.dropDownOpen;
- Doc.UnBrushAllDocs();
- })}>
- {this.Icon(color)}
- {!this.label || !FontIconBox.ShowIconLabels ? null : (
- <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
- {' '}
- {this.label}{' '}
- </div>
- )}
- <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
- </div>
- {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null}
- </div>
- );
- }
-
dropdownItemDown = (e: React.PointerEvent, value: string | number) => {
setupMoveUpEvents(
this,
e,
(e: PointerEvent) => {
- return ScriptCast(this.rootDoc.onDragScript)?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: { doc: value, e } }).result;
+ return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result;
},
emptyFunction,
emptyFunction
@@ -201,7 +175,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
* Dropdown list
*/
@computed get dropdownListButton() {
- const script = ScriptCast(this.rootDoc.script);
+ const script = ScriptCast(this.Document.script);
let noviceList: string[] = [];
let text: string | undefined;
@@ -209,7 +183,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
let icon: IconProp = 'caret-down';
const isViewDropdown = script?.script.originalScript.startsWith('setView');
if (isViewDropdown) {
- const selected = SelectionManager.Docs();
+ const selected = SelectionManager.Docs;
if (selected.lastElement()) {
if (StrCast(selected.lastElement().type) === DocumentType.COL) {
text = StrCast(selected.lastElement()._type_collection);
@@ -237,7 +211,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
} else {
- text = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+ text = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
// text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
getStyle = (val: string) => ({ fontFamily: val });
}
@@ -255,7 +229,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
return (
<Dropdown
selectedVal={text}
- setSelectedVal={undoable(value => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), `dropdown select ${this.label}`)}
+ setSelectedVal={undoable(value => script.script.run({ this: this.Document, self: this.Document, value }), `dropdown select ${this.label}`)}
color={SettingsManager.userColor}
background={SettingsManager.userVariantColor}
type={Type.TERT}
@@ -270,7 +244,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
@computed get colorScript() {
- return ScriptCast(this.rootDoc.script);
+ return ScriptCast(this.Document.script);
}
colorBatch: UndoManager.Batch | undefined;
@@ -278,18 +252,18 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
* Color button
*/
@computed get colorButton() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
- const tooltip: string = StrCast(this.rootDoc.toolTip);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
+ const tooltip: string = StrCast(this.Document.toolTip);
return (
<ColorPicker
setSelectedColor={value => {
if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
- this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+ this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
}}
setFinalColor={value => {
- this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+ this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
this.colorBatch?.end();
this.colorBatch = undefined;
}}
@@ -306,13 +280,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
@computed get multiToggleButton() {
// Determine the type of toggle button
- const tooltip: string = StrCast(this.rootDoc.toolTip);
+ const tooltip: string = StrCast(this.Document.toolTip);
- const script = ScriptCast(this.rootDoc.onClick);
- const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+ 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.rootDoc, this.props, StyleProp.Color);
- const items = DocListCast(this.rootDoc.data);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const items = DocListCast(this.dataDoc.data);
return (
<MultiToggle
tooltip={`Toggle ${tooltip}`}
@@ -320,7 +294,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
color={color}
background={SettingsManager.userBackgroundColor}
label={this.label}
- items={DocListCast(this.rootDoc.data).map(item => ({
+ items={DocListCast(this.dataDoc.data).map(item => ({
icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />,
tooltip: StrCast(item.toolTip),
val: StrCast(item.toolType),
@@ -336,14 +310,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
@computed get toggleButton() {
// Determine the type of toggle button
- const buttonText: string = StrCast(this.rootDoc.buttonText);
- const tooltip: string = StrCast(this.rootDoc.toolTip);
+ const buttonText = StrCast(this.dataDoc.buttonText);
+ const tooltip = StrCast(this.Document.toolTip);
- const script = ScriptCast(this.rootDoc.onClick);
- const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+ 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.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
return (
<Toggle
@@ -356,7 +330,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
//background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
- onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
+ onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })}
/>
);
}
@@ -365,20 +339,20 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
* Default
*/
@computed get defaultButton() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const tooltip: string = StrCast(this.rootDoc.toolTip);
+ 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.rootDoc.script);
+ const script = ScriptCast(this.Document.script);
// Function to run the script
- const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+ 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.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).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) => {}} />;
@@ -386,16 +360,16 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
<div className="menuButton editableText">
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
<div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
- <EditableView GetValue={() => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+ <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
</div>
</div>
);
}
render() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const tooltip = StrCast(this.rootDoc.toolTip);
- const scriptFunc = () => ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false });
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const tooltip = StrCast(this.Document.toolTip);
+ const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false });
const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
// prettier-ignore
switch (this.type) {
@@ -405,15 +379,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
case ButtonType.EditableText: return this.editableText;
case ButtonType.DropdownList: return this.dropdownListButton;
case ButtonType.ColorButton: return this.colorButton;
- case ButtonType.DropdownButton: return this.dropdownButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
case ButtonType.ToggleButton: return this.toggleButton;
case ButtonType.ClickButton:
case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
case ButtonType.TextButton: return <Button {...btnProps} color={color}
- background={SettingsManager.userBackgroundColor} text={StrCast(this.rootDoc.buttonText)}/>;
+ 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} />;
+ background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
}
return this.defaultButton;
}
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 40f48dafe..c26579e66 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -1,27 +1,19 @@
import functionPlot from 'function-plot';
-import { action, computed, reaction } from 'mobx';
+import { computed, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
-import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
+import { listSpec } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { DocFocusOptions, DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
-const EquationSchema = createSchema({});
-
-type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>;
-const EquationDocument = makeInterface(EquationSchema, documentSchema);
-
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
@@ -31,23 +23,23 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
_plot: any;
_plotId = '';
_plotEle: any;
+
constructor(props: any) {
super(props);
+ makeObservable(this);
this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
}
+
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
reaction(
- () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.rootDoc.xRange, this.rootDoc.yRange],
+ () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
() => this.createGraph()
);
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const anchor = Docs.Create.ConfigDocument({
- //
- annotationOn: this.rootDoc,
- });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.rootDoc);
+ const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.Document);
anchor.config_xRange = new List<number>(Array.from(this._plot.options.xAxis.domain));
anchor.config_yRange = new List<number>(Array.from(this._plot.options.yAxis.domain));
if (addAsAnnotation) this.addDocument(anchor);
@@ -55,8 +47,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
};
createGraph = (ele?: HTMLDivElement) => {
this._plotEle = ele || this._plotEle;
- const width = this.props.PanelWidth();
- const height = this.props.PanelHeight();
+ const width = this._props.PanelWidth();
+ const height = this._props.PanelHeight();
const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
try {
this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
@@ -64,8 +56,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
target: '#' + this._plotEle.id,
width,
height,
- xAxis: { domain: Cast(this.rootDoc.xRange, listSpec('number'), [-10, 10]) },
- yAxis: { domain: Cast(this.rootDoc.yRange, listSpec('number'), [-1, 9]) },
+ xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) },
+ yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) },
grid: true,
data: fns.map(fn => ({
fn,
@@ -80,7 +72,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
- const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true);
+ const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
@@ -105,14 +97,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
<div
ref={this.createDropTarget}
style={{
- pointerEvents: !this.props.isContentActive() ? 'all' : undefined,
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
+ pointerEvents: !this._props.isContentActive() ? 'all' : undefined,
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
}}>
{this.theGraph}
<div
style={{
- display: this.props.isSelected() ? 'none' : undefined,
+ display: this._props.isSelected() ? 'none' : undefined,
position: 'absolute',
width: '100%',
height: '100%',
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 103843046..876f13370 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,25 +1,20 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { Tooltip } from '@mui/material';
+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 { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { createSchema } from '../../../fields/Schema';
-import { ComputedField } from '../../../fields/ScriptField';
import { Cast, 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 { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
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';
@@ -31,22 +26,9 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
-import React = require('react');
-
-export const pageSchema = createSchema({
- googlePhotosUrl: 'string',
- googlePhotosTags: 'string',
-});
-const uploadIcons = {
- idle: 'downarrow.png',
- loading: 'loading.gif',
- success: 'greencheck.png',
- failure: 'redx.png',
-};
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -54,10 +36,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return FieldView.LayoutString(ImageBox, fieldKey);
}
- @observable public static imageRootDoc: Doc | undefined;
+ @observable public static imageRootDoc: Doc | undefined = undefined;
@observable public static imageEditorOpen: boolean = false;
@observable public static imageEditorSource: string = '';
- @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+ @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined = undefined;
@action public static setImageEditorOpen(open: boolean) {
ImageBox.imageEditorOpen = open;
}
@@ -72,16 +54,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _overlayIconRef = React.createRef<HTMLDivElement>();
private _marqueeref = React.createRef<MarqueeAnnotator>();
@observable _curSuffix = '';
- @observable _uploadIcon = uploadIcons.idle;
constructor(props: any) {
super(props);
- this.props.setContentView?.(this);
+ makeObservable(this);
+ this._props.setContentView?.(this);
}
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -89,27 +71,27 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const anchor =
visibleAnchor ??
Docs.Create.ConfigDocument({
- title: 'ImgAnchor:' + this.rootDoc.title,
+ title: 'ImgAnchor:' + this.Document.title,
config_panX: NumCast(this.layoutDoc._freeform_panX),
config_panY: NumCast(this.layoutDoc._freeform_panY),
config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
- annotationOn: this.rootDoc,
+ annotationOn: this.Document,
});
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.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document);
return anchor;
}
- return this.rootDoc;
+ return this.Document;
};
componentDidMount() {
this._disposers.sizer = reaction(
() => ({
- forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
- scrSize: (this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.rootDoc._freeform_scale, 1),
- selected: this.props.isSelected(),
+ forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
+ scrSize: (this._props.ScreenToLocalTransform().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'),
{ fireImmediately: true, delay: 1000 }
@@ -141,7 +123,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
let added: boolean | undefined = undefined;
@@ -152,7 +133,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
- this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
+ this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
}, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
@@ -177,13 +158,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@undoBatch
setNativeSize = action(() => {
- const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.rootDoc._freeform_scale, 1);
- const nscale = NumCast(this.props.PanelWidth()) / scaling;
+ const scaling = (this._props.DocumentView?.()._props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+ const nscale = NumCast(this._props.PanelWidth()) / scaling;
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
- this.rootDoc._freeform_panX = nw * NumCast(this.rootDoc._freeform_panX);
- this.rootDoc._freeform_panY = nw * NumCast(this.rootDoc._freeform_panY);
+ this.layoutDoc._freeform_panX = nw * NumCast(this.layoutDoc._freeform_panX);
+ this.layoutDoc._freeform_panY = nw * NumCast(this.layoutDoc._freeform_panY);
this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nw * NumCast(this.dataDoc._freeform_panXMax) : undefined;
this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nw * NumCast(this.dataDoc._freeform_panXMin) : undefined;
this.dataDoc._freeform_panYMax = this.dataDoc._freeform_panYMax ? nw * NumCast(this.dataDoc._freeform_panYMax) : undefined;
@@ -206,28 +187,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
+ Doc.GetProto(region).title = 'region:' + this.Document.title;
Doc.GetProto(region).followLinkToggle = true;
this.addDocument(region);
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
const anchw = NumCast(cropping._width);
const anchh = NumCast(cropping._height);
- const viewScale = NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']) / anchw;
- cropping.title = 'crop: ' + this.rootDoc.title;
- cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
- cropping.y = NumCast(this.rootDoc.y);
- cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
- cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw;
+ cropping.title = 'crop: ' + this.Document.title;
+ cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
+ cropping.y = NumCast(this.Document.y);
+ cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.onClick = undefined;
const croppingProto = Doc.GetProto(cropping);
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
croppingProto.backgroundColor = undefined;
- croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ 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 = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
+ croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
croppingProto['data_nativeWidth'] = anchw;
croppingProto['data_nativeHeight'] = anchh;
croppingProto.freeform_scale = viewScale;
@@ -240,12 +221,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
croppingProto.freeform_panY_max = anchh / viewScale;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
- cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
- cropping.y = NumCast(this.rootDoc.y);
- this.props.addDocTab(cropping, OpenWhere.inParent);
+ cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
+ cropping.y = NumCast(this.Document.y);
+ this._props.addDocTab(cropping, OpenWhere.inParent);
}
DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
return cropping;
};
@@ -262,55 +243,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
event: action(() => {
ImageBox.setImageEditorOpen(true);
ImageBox.setImageEditorSource(this.choosePath(field.url));
- ImageBox.addDoc = this.props.addDocument;
- ImageBox.imageRootDoc = this.rootDoc;
+ ImageBox.addDoc = this._props.addDocument;
+ ImageBox.imageRootDoc = this.Document;
}),
icon: 'pencil-alt',
});
- if (!Doc.noviceMode) {
- funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
-
- const existingAnalyze = ContextMenu.Instance?.findByDescription('Analyzers...');
- const modes: ContextMenuProps[] = existingAnalyze && 'subitems' in existingAnalyze ? existingAnalyze.subitems : [];
- modes.push({ description: 'Generate Tags', event: this.generateMetadata, icon: 'tag' });
- modes.push({ description: 'Find Faces', event: this.extractFaces, icon: 'camera' });
- //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
- !existingAnalyze && ContextMenu.Instance?.addItem({ description: 'Analyzers...', subitems: modes, icon: 'hand-point-right' });
- }
-
ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
};
- extractFaces = () => {
- const converter = (results: any) => {
- return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
- };
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-faces'], this.url, Service.Face, converter);
- };
-
- generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
- const converter = (results: any) => {
- const tagDoc = new Doc();
- const tagsList = new List();
- results.tags.map((tag: Tag) => {
- tagsList.push(tag.name);
- const sanitized = tag.name.replace(' ', '_');
- tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
- });
- this.dataDoc[this.fieldKey + '-generatedTags'] = tagsList;
- tagDoc.title = 'Generated Tags Doc';
- tagDoc.confidence = threshold;
- return tagDoc;
- };
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-generatedTagsDoc'], this.url, Service.ComputerVision, converter);
- };
-
- @computed private get url() {
- const data = Cast(this.dataDoc[this.fieldKey], ImageField);
- return data ? data.url?.href : undefined;
- }
-
choosePath(url: URL) {
if (!url?.href) return '';
const lower = url.href.toLowerCase();
@@ -319,62 +260,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
- return url.href.replace(ext, this._curSuffix + ext);
- }
-
- considerGooglePhotosLink = () => {
- const remoteUrl = StrCast(this.dataDoc.googlePhotosUrl); // bcz: StrCast or URLCast???
- return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />;
- };
-
- considerGooglePhotosTags = () => {
- const tags = this.dataDoc.googlePhotosTags;
- return !tags ? null : <img id={'google-tags'} src={'/assets/google_tags.png'} />;
- };
-
- getScrollHeight = () => (this.props.layout_fitWidth?.(this.rootDoc) !== false && NumCast(this.rootDoc._freeform_scale, 1) === NumCast(this.rootDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
-
- @computed
- private get considerDownloadIcon() {
- const data = this.dataDoc[this.fieldKey];
- if (!(data instanceof ImageField)) {
- return null;
- }
- const primary = data.url?.href;
- if (primary.includes(window.location.origin)) {
- return null;
- }
- return (
- <img
- id={'upload-icon'}
- draggable={false}
- style={{ transformOrigin: 'bottom right' }}
- src={`/assets/${this._uploadIcon}`}
- onClick={async () => {
- const { dataDoc } = this;
- const { success, failure, idle, loading } = uploadIcons;
- runInAction(() => (this._uploadIcon = loading));
- const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [primary] });
- dataDoc[this.props.fieldKey + '-originalUrl'] = primary;
- let succeeded = true;
- let data: ImageField | undefined;
- try {
- data = new ImageField(accessPaths.agnostic.client);
- } catch {
- succeeded = false;
- }
- runInAction(() => (this._uploadIcon = succeeded ? success : failure));
- setTimeout(
- action(() => {
- this._uploadIcon = idle;
- data && (dataDoc[this.fieldKey] = data);
- }),
- 2000
- );
- }}
- />
- );
+ return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
}
+ getScrollHeight = () => (this._props.layout_fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
@computed get nativeSize() {
TraceMobx();
@@ -384,7 +272,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return { nativeWidth, nativeHeight, nativeOrientation };
}
@computed get overlayImageIcon() {
- const usePath = this.rootDoc[`_${this.fieldKey}_usePath`];
+ const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
return (
<Tooltip
title={
@@ -405,9 +293,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="imageBox-alternateDropTarget"
ref={this._overlayIconRef}
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
+ onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -430,11 +318,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
}
+ @observable _error = '';
+
@observable _isHovering = false; // flag to switch between primary and alternate images on hover
@computed get content() {
TraceMobx();
- const backColor = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -452,21 +342,18 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
transformOrigin = 'right top';
transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
}
- const usePath = this.rootDoc[`_${this.fieldKey}_usePath`];
+ 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-fader" style={{ opacity: backAlpha }}>
- <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+ <img 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} />
</div>
)}
</div>
- {!Doc.noviceMode && this.considerDownloadIcon}
- {this.considerGooglePhotosLink()}
- <FaceRectangles document={this.dataDoc} color={'#0000FF'} backgroundColor={'#0000FF'} />
{this.overlayImageIcon}
</div>
);
@@ -477,11 +364,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
TraceMobx();
- return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
+ return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
}
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.props.ScreenToLocalTransform().Scale);
+ screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this._props.ScreenToLocalTransform().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._freeform_scale, 1) <= NumCast(this.rootDoc.freeform_scaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -500,7 +387,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
finishMarquee = () => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeref.current?.onTerminateSelection();
- this.props.select(false);
+ this._props.select(false);
};
focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
@@ -508,8 +395,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
- const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
- const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.NativeDimScaling?.() || 1)}px` : borderRad;
+ const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
+ const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
return (
<div
className="imageBox"
@@ -525,25 +412,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
})}
style={{
- width: this.props.PanelWidth() ? undefined : `100%`,
- height: this.props.PanelWidth() ? undefined : `100%`,
+ width: this._props.PanelWidth() ? undefined : `100%`,
+ height: this._props.PanelWidth() ? undefined : `100%`,
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this.layoutDoc.layout_fitWidth || this.props.layout_fitWidth?.(this.rootDoc) ? 'auto' : undefined,
+ overflow: this.layoutDoc.layout_fitWidth || this._props.layout_fitWidth?.(this.Document) ? 'auto' : undefined,
}}>
<CollectionFreeFormView
ref={this._ffref}
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
select={emptyFunction}
focus={this.focus}
@@ -559,12 +446,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{this.annotationLayer}
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
- rootDoc={this.rootDoc}
+ Document={this.Document}
+ ref={this._marqueeref}
scrollTop={0}
annotationLayerScrollTop={0}
scaling={returnOne}
- annotationLayerScaling={this.props.NativeDimScaling}
- docView={this.props.DocumentView!}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ docView={this._props.DocumentView!}
addDocument={this.addDocument}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index ffcba4981..a44f614b2 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,7 +1,7 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
.keyValueBox-cont {
overflow-y: scroll;
- width:100%;
+ width: 100%;
height: 100%;
background-color: $white;
border: 1px solid $medium-gray;
@@ -15,45 +15,45 @@
}
$header-height: 30px;
.keyValueBox-tbody {
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: absolute;
overflow-y: scroll;
}
.keyValueBox-key {
display: inline-block;
- height:100%;
- width:50%;
+ height: 100%;
+ width: 50%;
text-align: center;
}
.keyValueBox-fields {
display: inline-block;
- height:100%;
- width:50%;
+ height: 100%;
+ width: 50%;
text-align: center;
}
.keyValueBox-table {
position: absolute;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
border-collapse: collapse;
}
.keyValueBox-td-key {
- display:inline-block;
- height:30px;
+ display: inline-block;
+ height: 30px;
}
.keyValueBox-td-value {
- display:inline-block;
- height:30px;
+ display: inline-block;
+ height: 30px;
}
.keyValueBox-valueRow {
- width:100%;
- height:30px;
+ width: 100%;
+ height: 30px;
display: inline-block;
}
.keyValueBox-header {
- width:100%;
+ width: 100%;
position: relative;
display: inline-block;
background: $medium-gray;
@@ -74,8 +74,8 @@ $header-height: 30px;
.keyValueBox-evenRow {
position: relative;
display: flex;
- width:100%;
- height:$header-height;
+ width: 100%;
+ height: $header-height;
background: $white;
.formattedTextBox-cont {
background: $white;
@@ -86,10 +86,10 @@ $header-height: 30px;
position: relative;
}
}
-.keyValueBox-dividerDraggerThumb{
+.keyValueBox-dividerDraggerThumb {
position: relative;
width: 4px;
- float: left;
+ float: left;
height: 30px;
width: 5px;
z-index: 20;
@@ -99,10 +99,10 @@ $header-height: 30px;
background: black;
pointer-events: all;
}
-.keyValueBox-dividerDragger{
- position: relative;
+.keyValueBox-dividerDragger {
+ position: relative;
width: 100%;
- float: left;
+ float: left;
height: 37px;
z-index: 20;
right: 0;
@@ -114,10 +114,10 @@ $header-height: 30px;
.keyValueBox-oddRow {
position: relative;
display: flex;
- width:100%;
- height:30px;
+ width: 100%;
+ height: 30px;
background: $light-gray;
.formattedTextBox-cont {
background: $light-gray;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 95aeb5331..73fdc3a23 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,26 +1,27 @@
-import { action, computed, observable } from 'mobx';
+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 { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { DocCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { returnAll, returnAlways, returnTrue } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
-import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
+import { CompileScript, CompiledScript, ScriptOptions } from '../../util/Scripting';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentIconContainer } from './DocumentIcon';
import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { ImageBox } from './ImageBox';
import './KeyValueBox.scss';
import { KeyValuePair } from './KeyValuePair';
-import React = require('react');
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
export type KVPScript = {
script: CompiledScript;
@@ -28,10 +29,14 @@ export type KVPScript = {
onDelegate: boolean;
};
@observer
-export class KeyValueBox extends React.Component<FieldViewProps> {
+export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
public static LayoutString() {
return FieldView.LayoutString(KeyValueBox, 'data');
}
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
private _mainCont = React.createRef<HTMLDivElement>();
private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
@@ -39,19 +44,18 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
private _valInput = React.createRef<HTMLInputElement>();
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
- ignoreNativeDimScaling = returnTrue;
+ isKeyValueBox = returnTrue;
able = returnAlways;
layout_fitWidth = returnTrue;
- overridePointerEvents = returnAll;
onClickScriptDisable = returnAlways;
@observable private rows: KeyValuePair[] = [];
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this.props.fieldKey ? DocCast(this.props.Document[this.props.fieldKey], DocCast(this.props.Document)) : this.props.Document;
+ return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
}
@action
@@ -110,7 +114,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected(true)) {
+ if (e.buttons === 1 && this._props.isSelected()) {
e.stopPropagation();
}
};
@@ -156,8 +160,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
rows.push(
<KeyValuePair
doc={realDoc}
- addDocTab={this.props.addDocTab}
- PanelWidth={this.props.PanelWidth}
+ addDocTab={this._props.addDocTab}
+ PanelWidth={this._props.PanelWidth}
PanelHeight={this.rowHeight}
ref={(function () {
let oldEl: KeyValuePair | undefined;
@@ -221,19 +225,19 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
getFieldView = () => {
const rows = this.rows.filter(row => row.isChecked);
if (rows.length > 1) {
- const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true });
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true });
for (const row of rows) {
- const field = this.createFieldView(DocCast(this.props.Document), 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;
+ return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
- const metaKey = row.props.keyName;
+ const metaKey = row._props.keyName;
const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
fieldTemplate.title = metaKey;
fieldTemplate.layout_fitWidth = true;
@@ -279,8 +283,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
openItems.push({
description: 'Default Perspective',
event: () => {
- this.props.addDocTab(this.props.Document, OpenWhere.close);
- this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
+ this._props.addDocTab(this._props.Document, OpenWhere.close);
+ this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
},
icon: 'image',
});
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 57d36932e..46ea9c18e 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
.keyValuePair-td-key {
display: inline-block;
@@ -26,6 +26,7 @@
position: relative;
overflow: auto;
display: inline;
+ align-self: center;
}
}
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 6f99b7c28..fd8d8ef56 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,21 +1,23 @@
-import { action, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, Field } from '../../../fields/Doc';
+import { ObservableGroupMap } from 'mobx-utils';
+import * as React from 'react';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { Doc, Field } from '../../../fields/Doc';
+import { DocCast } from '../../../fields/Types';
+import { DocumentOptions, FInfo } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { EditableView } from '../EditableView';
import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, OpenWhereMod } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './DocumentView';
+import { FieldViewProps } from './FieldView';
import { KeyValueBox } from './KeyValueBox';
import './KeyValueBox.scss';
import './KeyValuePair.scss';
-import React = require('react');
-import { DocCast } from '../../../fields/Types';
-import { Tooltip } from '@material-ui/core';
-import { DocumentOptions, FInfo } from '../../documents/Documents';
+import { ObservableReactComponent } from '../ObservableReactComponent';
// Represents one row in a key value plane
@@ -29,10 +31,15 @@ export interface KeyValuePairProps {
addDocTab: (doc: Doc, where: OpenWhere) => boolean;
}
@observer
-export class KeyValuePair extends React.Component<KeyValuePairProps> {
+export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
@observable private isPointerOver = false;
@observable public isChecked = false;
private checkbox = React.createRef<HTMLInputElement>();
+ constructor(props:any) {
+ super(props);
+ makeObservable(this);
+ }
+
@action
handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -46,26 +53,24 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
};
onContextMenu = (e: React.MouseEvent) => {
- const value = this.props.doc[this.props.keyName];
+ const value = this._props.doc[this._props.keyName];
if (value instanceof Doc) {
e.stopPropagation();
e.preventDefault();
- ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
+ ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this._props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
}
};
render() {
const props: FieldViewProps = {
- Document: this.props.doc,
- DataDoc: this.props.doc,
+ Document: this._props.doc,
childFilters: returnEmptyFilter,
childFiltersByRanges: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
styleProvider: DefaultStyleProvider,
docViewPath: returnEmptyDoclist,
- fieldKey: this.props.keyName,
- rootSelected: returnFalse,
+ fieldKey: this._props.keyName,
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
@@ -75,12 +80,11 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
whenChildContentsActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
- PanelWidth: this.props.PanelWidth,
- PanelHeight: this.props.PanelHeight,
+ PanelWidth: this._props.PanelWidth,
+ PanelHeight: this._props.PanelHeight,
addDocTab: returnFalse,
pinToPres: returnZero,
};
- const contents = <FieldView {...props} />;
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
let doc: Doc | undefined = props.Document;
@@ -97,8 +101,8 @@ export class KeyValuePair extends React.Component<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))}>
- <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
+ <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
style={hover}
@@ -120,9 +124,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
</Tooltip>
</div>
</td>
- <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
+ <td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
<div className="keyValuePair-td-value-container">
- <EditableView contents={contents} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+ <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
</div>
</td>
</tr>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 4439be0cd..934bce448 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,10 +1,10 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -29,21 +29,27 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
componentWillUnMount() {
this._timeout && clearTimeout(this._timeout);
}
- getTitle() {
- return this.rootDoc.title_custom ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
+ @computed get Title() {
+ return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document);
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document);
}
};
@@ -66,7 +72,6 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
};
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
const docDragData = de.complete.docDragData;
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
@@ -81,16 +86,31 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
@observable _mouseOver = false;
@computed get hoverColor() {
- return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
}
- fitTextToBox = (r: any) => {
- const singleLine = BoolCast(this.rootDoc._singleLine, true);
+ fitTextToBox = (
+ r: any
+ ):
+ | NodeJS.Timeout
+ | {
+ rotateText: null;
+ fontSizeFactor: number;
+ minimumFontSize: number;
+ maximumFontSize: number;
+ limitingDimension: string;
+ horizontalAlign: string;
+ verticalAlign: string;
+ textAlign: string;
+ singleLine: boolean;
+ whiteSpace: string;
+ } => {
+ const singleLine = BoolCast(this.layoutDoc._singleLine, true);
const params = {
rotateText: null,
fontSizeFactor: 1,
- minimumFontSize: NumCast(this.rootDoc._label_minFontSize, 8),
- maximumFontSize: NumCast(this.rootDoc._label_maxFontSize, 1000),
+ minimumFontSize: NumCast(this.layoutDoc._label_minFontSize, 8),
+ maximumFontSize: NumCast(this.layoutDoc._label_maxFontSize, 1000),
limitingDimension: 'both',
horizontalAlign: 'center',
verticalAlign: 'center',
@@ -119,7 +139,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
- const label = this.getTitle();
+ const label = this.Title;
return (
<div
className="labelBox-outerDiv"
@@ -127,7 +147,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
onMouseOver={action(() => (this._mouseOver = true))}
ref={this.createDropTarget}
onContextMenu={this.specificContextMenu}
- style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
+ style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
<div
className="labelBox-mainButton"
style={{
@@ -137,15 +157,15 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
fontFamily: StrCast(this.layoutDoc._text_fontFamily) || 'inherit',
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
textTransform: StrCast(this.layoutDoc.textTransform) as any,
- paddingLeft: NumCast(this.rootDoc._xPadding),
- paddingRight: NumCast(this.rootDoc._xPadding),
- paddingTop: NumCast(this.rootDoc._yPadding),
- paddingBottom: NumCast(this.rootDoc._yPadding),
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
- whiteSpace: typeof boxParams !== 'number' && boxParams.singleLine ? 'pre' : 'pre-wrap',
+ paddingLeft: NumCast(this.layoutDoc._xPadding),
+ paddingRight: NumCast(this.layoutDoc._xPadding),
+ paddingTop: NumCast(this.layoutDoc._yPadding),
+ paddingBottom: NumCast(this.layoutDoc._yPadding),
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
+ whiteSpace: 'singleLine' in boxParams && boxParams.singleLine ? 'pre' : 'pre-wrap',
}}>
- <span style={{ width: typeof boxParams !== 'number' && boxParams.singleLine ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
+ <span style={{ width: 'singleLine' in boxParams ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
{label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
</span>
</div>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index fd7d13655..b86ba72a0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,9 +1,9 @@
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
+import { action, computed, makeObservable } from 'mobx';
+import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
import { SelectionManager } from '../../util/SelectionManager';
@@ -11,11 +11,8 @@ import { ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkAnchorBox.scss';
-import { LinkDocPreview } from './LinkDocPreview';
-import React = require('react');
-import globalCssVariables = require('../global/globalCssVariables.scss');
-
-@observer
+import { LinkInfo } from './LinkDocPreview';
+const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LinkAnchorBox, fieldKey);
@@ -25,21 +22,24 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
_ref = React.createRef<HTMLDivElement>();
_isOpen = false;
_timeout: NodeJS.Timeout | undefined;
- @observable _x = 0;
- @observable _y = 0;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
@computed get linkSource() {
- return this.props.docViewPath()[this.props.docViewPath().length - 2].rootDoc; // this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
+ return this._props.docViewPath()[this._props.docViewPath().length - 2].Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
}
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
- if (doubleTap) LinkFollower.FollowLink(this.rootDoc, this.linkSource, false);
- else this.props.select(false);
+ if (doubleTap) LinkFollower.FollowLink(this.Document, this.linkSource, false);
+ else this._props.select(false);
});
};
onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
@@ -49,15 +49,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
if (separation > 100) {
- const dragData = new DragManager.DocumentDragData([this.rootDoc]);
+ const dragData = new DragManager.DocumentDragData([this.Document]);
dragData.dropAction = 'embed';
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.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
- this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
- this.rootDoc.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;
@@ -67,28 +67,28 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
TraceMobx();
- const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView
- const x = NumCast(this.rootDoc[this.fieldKey + '_x'], 100);
- const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100);
- const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
+ const small = this._props.PanelWidth() <= 1; // this happens when rendered in a treeView
+ const x = NumCast(this.layoutDoc[this.fieldKey + '_x'], 100);
+ const y = NumCast(this.layoutDoc[this.fieldKey + '_y'], 100);
+ const background = this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.BackgroundColor + ':anchor');
const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1';
const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
- const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_1')
+ const selView = SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_1')
? 'link_anchor_1'
- : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_2')
- ? 'link_anchor_2'
- : '';
+ : SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_2')
+ ? 'link_anchor_2'
+ : '';
return (
<div
ref={this._ref}
title={targetTitle}
className={`linkAnchorBox-cont${small ? '-small' : ''}`}
onPointerEnter={e =>
- LinkDocPreview.SetLinkInfo({
- docProps: this.props,
+ LinkInfo.SetLinkInfo({
+ docProps: this._props,
linkSrc: this.linkSource,
- linkDoc: this.rootDoc,
+ linkDoc: this.Document,
showHeader: true,
location: [e.clientX, e.clientY + 20],
noPreview: false,
@@ -97,7 +97,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerDown={this.onPointerDown}
onContextMenu={this.specificContextMenu}
style={{
- border: selView && this.rootDoc[selView] === this.rootDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined,
+ border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined,
background,
left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 416cb11cc..134f2e14a 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,7 +1,7 @@
-import React = require('react');
import { Bezier } from 'bezier-js';
-import { computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
@@ -20,23 +20,28 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
return FieldView.LayoutString(LinkBox, fieldKey);
}
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
onClickScriptDisable = returnAlways;
@computed get anchor1() {
- const anchor1 = DocCast(this.rootDoc.link_anchor_1);
+ const anchor1 = DocCast(this.dataDoc.link_anchor_1);
const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
- return DocumentManager.Instance.getDocumentView(anchor_1, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
+ return DocumentManager.Instance.getDocumentView(anchor_1, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
}
@computed get anchor2() {
- const anchor2 = DocCast(this.rootDoc.link_anchor_2);
+ const anchor2 = DocCast(this.dataDoc.link_anchor_2);
const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
- return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
+ return DocumentManager.Instance.getDocumentView(anchor_2, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
}
screenBounds = () => {
if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
- const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
- const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) };
+ const a_invXf = this.anchor1._props.ScreenToLocalTransform().inverse();
+ const b_invXf = this.anchor2._props.ScreenToLocalTransform().inverse();
+ const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
+ const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
const pts = [] as number[][];
pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
@@ -54,19 +59,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
disposer: IReactionDisposer | undefined;
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
this.disposer = reaction(
() => {
if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
const a = (this.anchor1 ?? this.anchor2)!;
const b = (this.anchor2 ?? this.anchor1)!;
- const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform();
- const a_invXf = a.props.ScreenToLocalTransform().inverse();
- const b_invXf = b.props.ScreenToLocalTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) };
+ const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
+ const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this._props.ScreenToLocalTransform();
+ const a_invXf = a._props.ScreenToLocalTransform().inverse();
+ const b_invXf = b._props.ScreenToLocalTransform().inverse();
+ const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
+ const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
@@ -103,25 +108,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
componentWillUnmount(): void {
this.disposer?.();
}
- @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
+ @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
render() {
if (this.renderProps) {
- const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+ const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
const text = bez.get(0.5);
- const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
- const strokeWidth = NumCast(this.rootDoc.stroke_width, 4);
- const dash = StrCast(this.rootDoc.stroke_dash);
+ const linkDesc = StrCast(this.dataDoc.link_description) || 'description';
+ const strokeWidth = NumCast(this.dataDoc.stroke_width, 4);
+ const dash = StrCast(this.Document.stroke_dash);
const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
const { pts, lx, ty, rx, by } = this.renderProps;
return (
<div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
<svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
<defs>
- <filter x="0" y="0" width="1" height="1" id={`${this.rootDoc[Id] + 'background'}`}>
- <feFlood floodColor={`${StrCast(this.rootDoc._backgroundColor, 'lightblue')}`} result="bg" />
+ <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
+ <feFlood floodColor={`${StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
<feMerge>
<feMergeNode in="bg" />
<feMergeNode in="SourceGraphic" />
@@ -131,7 +136,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
<path
className="collectionfreeformlinkview-linkLine"
style={{
- pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
+ pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
stroke: highlightColor ?? 'lightblue',
strokeDasharray,
strokeWidth,
@@ -141,8 +146,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
/>
<text
- filter={`url(#${this.rootDoc[Id] + 'background'})`}
- style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
+ filter={`url(#${this.Document[Id] + 'background'})`}
+ style={{ pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
x={text.x - lx}
y={text.y - ty}>
<tspan>&nbsp;</tspan>
@@ -154,14 +159,14 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
return (
- <div className={`linkBox-container${this.props.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
+ <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
- {...this.props}
+ {...this._props}
fieldKey="link_anchor"
setHeight={emptyFunction}
dontRegisterView={true}
- renderDepth={this.props.renderDepth + 1}
- isContentActive={this.props.isContentActive}
+ renderDepth={this._props.renderDepth + 1}
+ isContentActive={this._props.isContentActive}
addDocument={returnFalse}
removeDocument={returnFalse}
moveDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index a8db5d360..104301656 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -1,7 +1,6 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
.linkDescriptionPopup {
-
display: flex;
flex-direction: row;
justify-content: center;
@@ -26,7 +25,6 @@
}
.linkDescriptionPopup-btn {
-
float: right;
justify-content: center;
vertical-align: middle;
@@ -53,6 +51,4 @@
color: black;
}
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index c45045a8a..32300d60a 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7e4f1da8e..d0a9f10b4 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import wiki from 'wikijs';
import { Doc, Opt } from '../../../fields/Doc';
@@ -18,7 +18,27 @@ import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
import './LinkDocPreview.scss';
-import React = require('react');
+import * as React from 'react';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+
+export class LinkInfo {
+ private static _instance: Opt<LinkInfo>;
+ constructor() {
+ LinkInfo._instance = this;
+ makeObservable(this);
+ }
+ @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));
+ }
+ public static SetLinkInfo(info?: LinkDocPreviewProps) {
+ runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+ }
+}
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -30,36 +50,25 @@ interface LinkDocPreviewProps {
noPreview?: boolean;
}
@observer
-export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
- @action public static Clear() {
- LinkDocPreview.LinkInfo = undefined;
- }
- @action public static SetLinkInfo(info?: LinkDocPreviewProps) {
- LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info);
- }
-
- static _instance: Opt<LinkDocPreview>;
-
+export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
_infoRef = React.createRef<HTMLDivElement>();
_linkDocRef = React.createRef<HTMLDivElement>();
- @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
- @observable _targetDoc: Opt<Doc>;
- @observable _markerTargetDoc: Opt<Doc>;
- @observable _linkDoc: Opt<Doc>;
- @observable _linkSrc: Opt<Doc>;
+ @observable _targetDoc: Opt<Doc> = undefined;
+ @observable _markerTargetDoc: Opt<Doc> = undefined;
+ @observable _linkDoc: Opt<Doc> = undefined;
+ @observable _linkSrc: Opt<Doc> = undefined;
@observable _toolTipText = '';
@observable _hrefInd = 0;
-
constructor(props: any) {
super(props);
- LinkDocPreview._instance = this;
+ makeObservable(this);
}
@action
init() {
- var linkTarget = this.props.linkDoc;
- this._linkSrc = this.props.linkSrc;
- this._linkDoc = this.props.linkDoc;
+ var 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) {
@@ -72,28 +81,28 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
this._toolTipText = '';
this.updateHref();
}
- componentDidUpdate(props: any) {
- if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
+ componentDidUpdate(prevProps: Readonly<LinkDocPreviewProps>) {
+ super.componentDidUpdate(prevProps);
+ if (prevProps.linkSrc !== this._props.linkSrc || prevProps.linkDoc !== this._props.linkDoc || prevProps.hrefs !== this._props.hrefs) this.init();
}
componentDidMount() {
this.init();
document.addEventListener('pointerdown', this.onPointerDown, true);
}
- @action
componentWillUnmount() {
- LinkDocPreview.SetLinkInfo(undefined);
+ LinkInfo.Clear();
document.removeEventListener('pointerdown', this.onPointerDown, true);
}
onPointerDown = (e: PointerEvent) => {
- !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+ !this._linkDocRef.current?.contains(e.target as any) && LinkInfo.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
};
@action
updateHref() {
- if (this.props.hrefs?.length) {
- const href = this.props.hrefs[this._hrefInd];
+ if (this._props.hrefs?.length) {
+ const href = this._props.hrefs[this._hrefInd];
if (href.indexOf(Doc.localServerPath()) !== 0) {
// link to a web page URL -- try to show a preview
if (href.startsWith('https://en.wikipedia.org/wiki/')) {
@@ -123,7 +132,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
this._markerTargetDoc = linkTarget;
this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
}
- if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
+ if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
}
})
);
@@ -143,9 +152,9 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
action(() => {
LinkManager.currentLink = this._linkDoc;
LinkManager.currentLinkAnchor = this._linkSrc;
- this.props.docProps.DocumentView?.().select(false);
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- SettingsManager.propertiesWidth = 250;
+ this._props.docProps.DocumentView?.().select(false);
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
})
);
@@ -157,7 +166,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
returnFalse,
emptyFunction,
action(() => {
- const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ const nextHrefInd = (this._hrefInd + 1) % (this._props.hrefs?.length || 1);
if (nextHrefInd !== this._hrefInd) {
this._linkDoc = undefined;
this._hrefInd = nextHrefInd;
@@ -168,19 +177,19 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
};
followLink = () => {
- LinkDocPreview.Clear();
+ LinkInfo.Clear();
if (this._linkDoc && this._linkSrc) {
LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
- } else if (this.props.hrefs?.length) {
+ } else if (this._props.hrefs?.length) {
const webDoc =
- Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ??
- Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
+ Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0]).keys()).lastElement() ??
+ Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
DocumentManager.Instance.showDocument(webDoc, {
openLocation: OpenWhere.lightbox,
willPan: true,
zoomTime: 500,
});
- //this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+ //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
}
};
@@ -216,7 +225,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
</div>
<div className="linkDocPreview-buttonBar" style={{ float: 'right' }}>
<Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
- <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
+ <div className="linkDocPreview-button" style={{ background: (this._props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
<FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
</div>
</Tooltip>
@@ -228,7 +237,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
@computed get docPreview() {
return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
<div className="linkDocPreview-inner">
- {!this.props.showHeader ? null : this.previewHeader}
+ {!this._props.showHeader ? null : this.previewHeader}
<div
className="linkDocPreview-preview-wrapper"
onPointerDown={e =>
@@ -238,7 +247,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
(e, down, delta) => {
if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
- LinkDocPreview.Clear();
+ LinkInfo.Clear();
return true;
}
return false;
@@ -256,12 +265,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
<DocumentView
ref={r => {
const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- targetanchor && this._targetDoc !== targetanchor && r?.props.focus?.(targetanchor, {});
+ targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {});
}}
Document={this._targetDoc!}
moveDocument={returnFalse}
- rootSelected={returnFalse}
- styleProvider={this.props.docProps?.styleProvider}
+ styleProvider={this._props.docProps?.styleProvider}
docViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
@@ -300,7 +308,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
className="linkDocPreview"
ref={this._linkDocRef}
onPointerDown={this.followLinkPointerDown}
- style={{ borderColor: SettingsManager.userColor, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+ style={{ borderColor: SettingsManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}>
{this.docPreview}
</div>
);
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 4bb0f14d2..27d73a585 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -6,6 +6,7 @@ import { Doc } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { StrCast } from '../../../fields/Types';
import { Networking } from '../../Network';
+import { DocumentManager } from '../../util/DocumentManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import './LoadingBox.scss';
@@ -36,49 +37,47 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LoadingBox, fieldKey);
}
- @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
// removes from currently loading display
- @action
public static removeCurrentlyLoading(doc: Doc) {
- if (LoadingBox.CurrentlyLoading) {
- const index = LoadingBox.CurrentlyLoading.indexOf(doc);
- index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+ if (DocumentManager.Instance.CurrentlyLoading) {
+ const index = DocumentManager.Instance.CurrentlyLoading.indexOf(doc);
+ runInAction(() => index !== -1 && DocumentManager.Instance.CurrentlyLoading.splice(index, 1));
}
}
// adds doc to currently loading display
- @action
public static addCurrentlyLoading(doc: Doc) {
- if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
- LoadingBox.CurrentlyLoading.push(doc);
+ if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) {
+ runInAction(() => DocumentManager.Instance.CurrentlyLoading.push(doc));
}
}
_timer: any;
@observable progress = '';
componentDidMount() {
- if (!LoadingBox.CurrentlyLoading?.includes(this.rootDoc)) {
- this.rootDoc.loadingError = 'Upload interrupted, please try again';
+ if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) {
+ this.Document.loadingError = 'Upload interrupted, please try again';
} else {
const updateFunc = async () => {
- const result = await Networking.QueryYoutubeProgress(StrCast(this.rootDoc[Id])); // We use the guid of the overwriteDoc to track file uploads.
+ 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));
- !this.rootDoc.loadingError && (this._timer = setTimeout(updateFunc, 1000));
+ !this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000));
};
this._timer = setTimeout(updateFunc, 1000);
}
}
componentWillUnmount() {
clearTimeout(this._timer);
+ this._timer = undefined;
}
render() {
return (
- <div className="loadingBoxContainer" style={{ background: !this.rootDoc.loadingError ? '' : 'red' }}>
+ <div className="loadingBoxContainer" style={{ background: !this.Document.loadingError ? '' : 'red' }}>
<div className="loadingBox-textContainer">
- <span className="loadingBox-title">{StrCast(this.rootDoc.title)}</span>
- <p className="loadingBox-headerText">{StrCast(this.rootDoc.loadingError, 'Loading ' + (this.progress.replace('[download]', '') || '(can take several minutes)'))}</p>
- {this.rootDoc.loadingError ? null : (
+ <span className="loadingBox-title">{StrCast(this.Document.title)}</span>
+ <p className="loadingBox-headerText">{StrCast(this.Document.loadingError, 'Loading ' + (this.progress.replace('[download]', '') || '(can take several minutes)'))}</p>
+ {this.Document.loadingError ? null : (
<div className="loadingBox-spinner">
<ReactLoading type="spinningBubbles" color="blue" height={100} width={100} />
</div>
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
new file mode 100644
index 000000000..d54a175b2
--- /dev/null
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -0,0 +1,35 @@
+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">
+ {`
+ .fil0 { fill: black; fill-rule: nonzero; }
+ .fil1 { fill: #FE0000; fill-rule: nonzero; }
+ `}
+ </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"/>
+ </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"/>
+ </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"/>
+ </svg>
+);
+
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
new file mode 100644
index 000000000..42dfa59b7
--- /dev/null
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -0,0 +1,450 @@
+import mapboxgl from 'mapbox-gl';
+import { MercatorCoordinate } from 'mapbox-gl';
+import { MapRef } from 'react-map-gl';
+import * as React from 'react';
+import * as d3 from 'd3';
+import * as turf from '@turf/turf';
+import { Position } from '@turf/turf';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+import { observer } from 'mobx-react';
+import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+
+export enum AnimationStatus {
+ START = 'start',
+ RESUME = 'resume',
+ RESTART = 'restart',
+}
+
+export enum AnimationSpeed {
+ SLOW = '1x',
+ MEDIUM = '2x',
+ FAST = '3x',
+}
+
+export class AnimationUtility {
+ private SMOOTH_FACTOR = 0.95;
+ private ROUTE_COORDINATES: Position[] = [];
+
+ @observable
+ private PATH?: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = undefined;
+
+ private PATH_DISTANCE: number = 0;
+ private FLY_IN_START_PITCH = 40;
+ private FIRST_LNG_LAT: { lng: number; lat: number } = { lng: 0, lat: 0 };
+ private START_ALTITUDE = 3_000_000;
+ private MAP_REF: MapRef | null = null;
+
+ @observable private isStreetViewAnimation: boolean = false;
+ @observable private animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+
+ @observable
+ private previousLngLat: { lng: number; lat: number };
+
+ private previousAltitude: number | null = null;
+ private previousPitch: number | null = null;
+
+ private currentStreetViewBearing: number = 0;
+
+ private terrainDisplayed: boolean;
+
+ @computed get flyInEndBearing() {
+ return this.isStreetViewAnimation
+ ? this.calculateBearing(
+ {
+ lng: this.ROUTE_COORDINATES[0][0],
+ lat: this.ROUTE_COORDINATES[0][1],
+ },
+ {
+ lng: this.ROUTE_COORDINATES[1][0],
+ lat: this.ROUTE_COORDINATES[1][1],
+ }
+ )
+ : -20;
+ }
+
+ @computed get currentAnimationAltitude(): number {
+ if (!this.isStreetViewAnimation) return 20_000;
+ if (!this.terrainDisplayed) return 50;
+ const coords: mapboxgl.LngLatLike = [this.previousLngLat.lng, this.previousLngLat.lat];
+ // console.log('MAP REF: ', this.MAP_REF)
+ // console.log("current elevation: ", this.MAP_REF?.queryTerrainElevation(coords));
+ let altitude = this.MAP_REF ? this.MAP_REF.queryTerrainElevation(coords) ?? 0 : 0;
+ if (altitude === 0) {
+ altitude += 50;
+ }
+ if (this.previousAltitude) {
+ return this.lerp(altitude, this.previousAltitude, 0.02);
+ }
+ return altitude;
+ }
+
+ @computed get flyInStartBearing() {
+ return Math.max(0, Math.min(this.flyInEndBearing + 20, 360)); // between 0 and 360
+ }
+
+ @computed get flyInEndAltitude() {
+ // return this.isStreetViewAnimation ? (this.currentAnimationAltitude + 70 ): 10_000;
+ return this.currentAnimationAltitude;
+ }
+
+ @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)));
+
+ 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() {
+ return this.currentPitch;
+ }
+
+ @computed get flyToDuration() {
+ switch (this.animationSpeed) {
+ case AnimationSpeed.SLOW:
+ return 4_000;
+ case AnimationSpeed.MEDIUM:
+ return 2_500;
+ case AnimationSpeed.FAST:
+ return 1_250;
+ default:
+ return 2_500;
+ }
+ }
+
+ @computed get animationDuration(): number {
+ let scalingFactor: number;
+ const MIN_DISTANCE = 0;
+ const MAX_DISTANCE = 3_000; // anything greater than 3000 is just set to 1 when normalized
+ const MAX_DURATION = this.isStreetViewAnimation ? 120_000 : 60_000;
+
+ const normalizedDistance = Math.min(1, (this.PATH_DISTANCE - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE));
+ const easedDistance = d3.easeExpOut(Math.min(normalizedDistance, 1));
+
+ switch (this.animationSpeed) {
+ case AnimationSpeed.SLOW:
+ scalingFactor = 250;
+ break;
+ case AnimationSpeed.MEDIUM:
+ scalingFactor = 150;
+ break;
+ case AnimationSpeed.FAST:
+ scalingFactor = 85;
+ break;
+ default:
+ scalingFactor = 150;
+ break;
+ }
+
+ const duration = Math.min(MAX_DURATION, easedDistance * MAX_DISTANCE * (this.isStreetViewAnimation ? scalingFactor * 1.12 : scalingFactor));
+
+ return duration;
+ }
+
+ @action
+ public updateAnimationSpeed(speed: AnimationSpeed) {
+ // calculate new flyToDuration and animationDuration
+ this.animationSpeed = speed;
+ }
+
+ @action
+ public updateIsStreetViewAnimation(isStreetViewAnimation: boolean) {
+ this.isStreetViewAnimation = isStreetViewAnimation;
+ }
+
+ @action
+ public setPath = (path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>) => {
+ this.PATH = path;
+ };
+
+ constructor(firstLngLat: { lng: number; lat: number }, routeCoordinates: Position[], isStreetViewAnimation: boolean, animationSpeed: AnimationSpeed, terrainDisplayed: boolean, mapRef: MapRef | null) {
+ makeObservable(this);
+ this.FIRST_LNG_LAT = firstLngLat;
+ this.previousLngLat = firstLngLat;
+ this.isStreetViewAnimation = isStreetViewAnimation;
+ this.MAP_REF = mapRef;
+
+ this.ROUTE_COORDINATES = routeCoordinates;
+ this.PATH = turf.lineString(routeCoordinates);
+ this.PATH_DISTANCE = turf.lineDistance(this.PATH);
+ this.terrainDisplayed = terrainDisplayed;
+
+ const bearing = this.calculateBearing(
+ {
+ lng: routeCoordinates[0][0],
+ lat: routeCoordinates[0][1],
+ },
+ {
+ lng: routeCoordinates[1][0],
+ lat: routeCoordinates[1][1],
+ }
+ );
+ this.currentStreetViewBearing = bearing;
+ this.animationSpeed = animationSpeed;
+ }
+
+ public animatePath = async ({
+ map,
+ // path,
+ // startBearing,
+ // startAltitude,
+ // pitch,
+ currentAnimationPhase,
+ updateAnimationPhase,
+ updateFrameId,
+ }: {
+ map: MapRef;
+ // path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>,
+ // startBearing: number,
+ // startAltitude: number,
+ // pitch: number,
+ currentAnimationPhase: number;
+ updateAnimationPhase: (newAnimationPhase: number) => void;
+ updateFrameId: (newFrameId: number) => void;
+ }) => {
+ return new Promise<void>(async resolve => {
+ let startTime: number | null = null;
+
+ const frame = async (currentTime: number) => {
+ if (!startTime) startTime = currentTime;
+ const elapsedSinceLastFrame = currentTime - startTime;
+ const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
+ const animationPhase = currentAnimationPhase + phaseIncrement;
+
+ // when the duration is complete, resolve the promise and stop iterating
+ if (animationPhase > 1) {
+ resolve();
+ return;
+ }
+
+ if (!this.PATH) return;
+ // calculate the distance along the path based on the animationPhase
+ const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry.coordinates;
+
+ const lngLat = {
+ lng: alongPath[0],
+ lat: alongPath[1],
+ };
+
+ let bearing: number;
+ if (this.isStreetViewAnimation) {
+ bearing = this.lerp(this.currentStreetViewBearing, this.calculateBearing(this.previousLngLat, lngLat), 0.032);
+ this.currentStreetViewBearing = bearing;
+ // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
+ } else {
+ // slowly rotate the map at a constant rate
+ bearing = this.flyInEndBearing - animationPhase * 200.0;
+ // bearing = startBearing - animationPhase * 200.0;
+ }
+
+ runInAction(() => {
+ this.previousLngLat = lngLat;
+ });
+
+ updateAnimationPhase(animationPhase);
+
+ // compute corrected camera ground position, so that he leading edge of the path is in view
+ var correctedPosition = this.computeCameraPosition(
+ this.isStreetViewAnimation,
+ this.currentPitch,
+ bearing,
+ lngLat,
+ this.currentAnimationAltitude,
+ true // smooth
+ );
+
+ // set the pitch and bearing of the camera
+ const camera = map.getFreeCameraOptions();
+ camera.setPitchBearing(this.currentPitch, bearing);
+
+ // set the position and altitude of the camera
+ camera.position = MercatorCoordinate.fromLngLat(correctedPosition, this.currentAnimationAltitude);
+
+ // apply the new camera options
+ map.setFreeCameraOptions(camera);
+
+ this.previousAltitude = this.currentAnimationAltitude;
+ this.previousPitch = this.previousPitch;
+
+ // repeat!
+ const innerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(innerFrameId);
+ };
+
+ 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 => {
+ let start: number | null;
+
+ var currentAltitude;
+ var currentBearing;
+ var currentPitch;
+
+ // the animation frame will run as many times as necessary until the duration has been reached
+ const frame = async (time: number) => {
+ if (!start) {
+ start = time;
+ }
+
+ // otherwise, use the current time to determine how far along in the duration we are
+ let animationPhase = (time - start) / this.flyToDuration;
+
+ // because the phase calculation is imprecise, the final zoom can vary
+ // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
+ if (animationPhase > 1) {
+ animationPhase = 1;
+ }
+
+ currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase);
+ // rotate the camera between startBearing and endBearing
+ currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase);
+
+ 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);
+
+ // set the pitch and bearing of the camera
+ const camera = map.getFreeCameraOptions();
+ camera.setPitchBearing(currentPitch, currentBearing);
+
+ // set the position and altitude of the camera
+ camera.position = MercatorCoordinate.fromLngLat(correctedPosition, currentAltitude);
+
+ // apply the new camera options
+ map.setFreeCameraOptions(camera);
+
+ // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
+ if (animationPhase === 1) {
+ resolve({
+ bearing: currentBearing,
+ altitude: currentAltitude,
+ });
+
+ // return so there are no further iterations of this frame
+ return;
+ }
+
+ const innerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(innerFrameId);
+ };
+
+ 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;
+ };
+
+ computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
+ const bearingInRadian = (bearing * Math.PI) / 180;
+ const pitchInRadian = ((90 - pitch) * Math.PI) / 180;
+
+ let correctedLng = targetPosition.lng;
+ let correctedLat = targetPosition.lat;
+
+ if (!isStreetViewAnimation) {
+ const lngDiff = ((altitude / Math.tan(pitchInRadian)) * Math.sin(-bearingInRadian)) / 70000; // ~70km/degree longitude
+ const latDiff = ((altitude / Math.tan(pitchInRadian)) * Math.cos(-bearingInRadian)) / 110000; // 110km/degree latitude
+
+ correctedLng = targetPosition.lng + lngDiff;
+ correctedLat = targetPosition.lat - latDiff;
+ }
+
+ const newCameraPosition = {
+ lng: correctedLng,
+ lat: correctedLat,
+ };
+
+ if (smooth) {
+ if (this.previousCameraPosition) {
+ newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
+ newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
+ }
+ }
+
+ this.previousCameraPosition = newCameraPosition;
+
+ return newCameraPosition;
+ };
+
+ public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties> => {
+ const coords = {
+ latitude: center[1],
+ longitude: center[0],
+ };
+ const km = radiusInKm;
+ const ret = [];
+ const distanceX = km / (111.32 * Math.cos((coords.latitude * Math.PI) / 180));
+ const distanceY = km / 110.574;
+ let theta;
+ let x;
+ let y;
+ for (let i = 0; i < points; i += 1) {
+ theta = (i / points) * (2 * Math.PI);
+ x = distanceX * Math.cos(theta);
+ y = distanceY * Math.sin(theta);
+ ret.push([coords.longitude + x, coords.latitude + y]);
+ }
+ ret.push(ret[0]);
+ return {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [ret],
+ },
+ properties: {},
+ };
+ };
+
+ private calculateBearing(from: { lng: number; lat: number }, to: { lng: number; lat: number }): number {
+ const lon1 = from.lng;
+ const lat1 = from.lat;
+ const lon2 = to.lng;
+ const lat2 = to.lat;
+
+ const lon1Rad = (lon1 * Math.PI) / 180;
+ const lon2Rad = (lon2 * Math.PI) / 180;
+ const lat1Rad = (lat1 * Math.PI) / 180;
+ const lat2Rad = (lat2 * Math.PI) / 180;
+
+ const y = Math.sin(lon2Rad - lon1Rad) * Math.cos(lat2Rad);
+ const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
+
+ let bearing = Math.atan2(y, x);
+
+ // Convert bearing from radians to degrees
+ bearing = (bearing * 180) / Math.PI;
+
+ // Ensure the bearing is within [0, 360)
+ if (bearing < 0) {
+ bearing += 360;
+ }
+
+ return bearing;
+ }
+}
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
new file mode 100644
index 000000000..f9607becf
--- /dev/null
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -0,0 +1,122 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { IconButton } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
+
+@observer
+export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ static Instance: DirectionsAnchorMenu;
+
+ private _disposer: IReactionDisposer | undefined;
+
+ public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
+ public Center: () => void = unimplementedFunction;
+ 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 Delete: () => void = unimplementedFunction;
+ // public MakeTargetToggle: () => void = unimplementedFunction;
+ // public ShowTargetTrail: () => void = unimplementedFunction;
+ public IsTargetToggler: () => boolean = returnFalse;
+
+ private title: string | undefined = undefined;
+
+ public setPinDoc(pinDoc: Doc) {
+ this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+ console.log('Title: ', this.title);
+ }
+
+ public get Active() {
+ return this._left > 0;
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ DirectionsAnchorMenu.Instance = this;
+ DirectionsAnchorMenu.Instance._canFade = false;
+ }
+
+ componentWillUnmount() {
+ this._disposer?.();
+ }
+
+ componentDidMount() {
+ this._disposer = reaction(
+ () => SelectionManager.Views.slice(),
+ sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+ );
+ }
+ // audioDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+ // };
+
+ // cropDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvents(
+ // this,
+ // e,
+ // (e: PointerEvent) => {
+ // this.StartCropDrag(e, this._commentCont.current!);
+ // return true;
+ // },
+ // returnFalse,
+ // e => this.OnCrop?.(e)
+ // );
+ // };
+ // notePointerDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvent(
+ // this,
+ // e,
+ // (e: PointerEvent) => {
+ // this.StartDrag(e, this._commentRef.current!);
+ // return true;
+ // },
+ // returnFalse,
+ // e => this.OnClick(e)
+ // );
+ // };
+
+ static top = React.createRef<HTMLDivElement>();
+
+ // public get Top(){
+ // return this.top
+ // }
+
+ render() {
+ const buttons = (
+ <div className="directions-menu-buttons" style={{ display: 'flex' }}>
+ <IconButton
+ tooltip="Add route" //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon={faAdd 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>
+ );
+
+ return this.getElement(
+ <div ref={DirectionsAnchorMenu.top} style={{ height: 'max-content', width: '100%', display: 'flex', flexDirection: 'column' }}>
+ <div>{this.title}</div>
+ <div className="direction-inputs" style={{ display: 'flex', flexDirection: 'column' }}>
+ <input placeholder="Origin" />
+ <input placeholder="Destination" />
+ </div>
+ {buttons}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
new file mode 100644
index 000000000..e4ba51316
--- /dev/null
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -0,0 +1,107 @@
+// import React from 'react';
+// import MapboxGeocoder , { GeocoderOptions} from '@mapbox/mapbox-gl-geocoder'
+// 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'>;
+// position: ControlPosition;
+
+// onLoading: (...args: any[]) => void;
+// onResults: (...args: any[]) => void;
+// onResult: (...args: any[]) => void;
+// onError: (...args: any[]) => void;
+// }
+
+// export const GeocoderControl = (props: GeocoderControlProps) => {
+
+// console.log(props);
+
+// const geocoder = useControl<MapboxGeocoder>(
+// () => {
+// const ctrl = new MapboxGeocoder({
+// ...props,
+// marker: false,
+// accessToken: props.mapboxAccessToken
+// });
+// ctrl.on('loading', props.onLoading);
+// ctrl.on('results', props.onResults);
+// ctrl.on('result', evt => {
+// props.onResult(evt);
+
+// // const {result} = evt;
+// // const location =
+// // result &&
+// // (result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates));
+// // if (location && props.marker) {
+// // setMarker(<Marker {...props.marker} longitude={location[0]} latitude={location[1]} />);
+// // } else {
+// // setMarker(null);
+// // }
+// });
+// ctrl.on('error', props.onError);
+// return ctrl;
+// },
+// {
+// position: props.position
+// }
+// );
+
+
+// // @ts-ignore (TS2339) private member
+// if (geocoder._map) {
+// if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
+// geocoder.setProximity(props.proximity);
+// }
+// if (geocoder.getRenderFunction() !== props.render && props.render !== undefined) {
+// geocoder.setRenderFunction(props.render);
+// }
+// if (geocoder.getLanguage() !== props.language && props.language !== undefined) {
+// geocoder.setLanguage(props.language);
+// }
+// if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) {
+// geocoder.setZoom(props.zoom);
+// }
+// if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) {
+// geocoder.setFlyTo(props.flyTo);
+// }
+// if (geocoder.getPlaceholder() !== props.placeholder && props.placeholder !== undefined) {
+// geocoder.setPlaceholder(props.placeholder);
+// }
+// if (geocoder.getCountries() !== props.countries && props.countries !== undefined) {
+// geocoder.setCountries(props.countries);
+// }
+// if (geocoder.getTypes() !== props.types && props.types !== undefined) {
+// geocoder.setTypes(props.types);
+// }
+// if (geocoder.getMinLength() !== props.minLength && props.minLength !== undefined) {
+// geocoder.setMinLength(props.minLength);
+// }
+// if (geocoder.getLimit() !== props.limit && props.limit !== undefined) {
+// geocoder.setLimit(props.limit);
+// }
+// if (geocoder.getFilter() !== props.filter && props.filter !== undefined) {
+// geocoder.setFilter(props.filter);
+// }
+// if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) {
+// geocoder.setOrigin(props.origin);
+// }
+// }
+// return (
+// <div>
+// Geocoder
+// </div>
+// )
+// }
+
+// const noop = () => {};
+
+// GeocoderControl.defaultProps = {
+// marker: true,
+// onLoading: noop,
+// onResults: noop,
+// onError: noop
+// }; \ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
index 6990bdcf1..c36d98afe 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.scss
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
@@ -51,4 +51,81 @@
border: 2px solid white;
}
}
-} \ No newline at end of file
+}
+
+.map-anchor-menu-container {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ padding: 5px;
+ height: max-content;
+ min-width: 300px;
+
+ .direction-inputs {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+
+ #get-routes-button {
+ padding: 8px 10px;
+ border-radius: 5px;
+ }
+ }
+
+ .MuiInputBase-input{
+ color: white !important;
+ }
+
+
+ .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled{
+ -webkit-text-fill-color: #b3b2b2 !important;
+ }
+
+ .current-route-info-container {
+ width: 100%;
+
+ .transportation-icons-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .selected-route-details-container{
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ justify-content: center;
+ align-items: flex-start;
+ padding: 5px;
+ }
+
+
+ }
+
+ .customized-marker-container{
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+
+ .current-marker-container{
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .all-markers-container{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+ max-width: 400px;
+ }
+ }
+
+
+
+
+}
+
+
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index f6680aac0..1b1b74e7c 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,15 +1,28 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
+import * as React from 'react';
+import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
import { SelectionManager } from '../../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
-import { IconButton } from 'browndash-components';
+import { Button, IconButton } from 'browndash-components';
import { SettingsManager } from '../../../util/SettingsManager';
import './MapAnchorMenu.scss';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { IconLookup, faDiamondTurnRight, faCalendarDays, faEdit, faAdd, faRoute, faArrowLeft, faLocationDot, faArrowDown, faCar, faBicycle, faPersonWalking, faUpload, faArrowsRotate } from '@fortawesome/free-solid-svg-icons';
+import { DirectionsAnchorMenu } from './DirectionsAnchorMenu';
+import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
+import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
+import { MapBox } from './MapBox';
+import { List } from '../../../../fields/List';
+import { MarkerIcons } from './MarkerIcons';
+import { CirclePicker, ColorResult } from 'react-color';
+import { Position } from 'geojson';
+import { CalendarManager } from '../../../util/CalendarManager';
+
+type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
@observer
export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -17,6 +30,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private _disposer: IReactionDisposer | undefined;
private _commentRef = React.createRef<HTMLDivElement>();
+ private _fileInputRef = React.createRef<HTMLInputElement>();
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -30,24 +44,91 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
public IsTargetToggler: () => boolean = returnFalse;
+
+ public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
+ public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction;
+ public CreatePin: (feature: any) => void = unimplementedFunction;
+
+ public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
+ public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
+
+
+ public Hide: () => void = unimplementedFunction;
+
+ public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
+
+ @observable
+ menuType: MapAnchorMenuType = 'standard';
+
+ @action
+ public setMenuType = (menuType: MapAnchorMenuType) => {
+ this.menuType = menuType;
+ };
+
+ private allMapPinDocs: Doc[] = [];
+
+ private pinDoc: Doc | undefined = undefined;
+
+ private routeDoc: Doc | undefined = undefined;
+
+ private title: string | undefined = undefined;
+
+ public setPinDoc(pinDoc: Doc | undefined) {
+ if (pinDoc){
+ this.pinDoc = pinDoc;
+ this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+ }
+
+ }
+
+ public setRouteDoc(routeDoc: Doc | undefined) {
+ if (routeDoc){
+ this.routeDoc = routeDoc;
+ this.title = StrCast(routeDoc.title ?? 'Map route');
+ }
+ }
+
+ @action
+ public Reset(){
+ this.destinationSelected = false;
+ this.currentRouteInfoMap = undefined;
+ this.destinationFeatures = [];
+ this.selectedDestinationFeature = undefined;
+ this.allMapPinDocs = [];
+ this.title = undefined;
+ this.routeDoc = undefined;
+ this.pinDoc = undefined;
+ }
+
+ public setAllMapboxPins(pinDocs: Doc[]) {
+ this.allMapPinDocs = pinDocs;
+ pinDocs.forEach((p, idx) => {
+ console.log(`Pin ${idx}: ${p.title}`);
+ });
+ }
+
public get Active() {
return this._left > 0;
}
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
-
+ makeObservable(this);
MapAnchorMenu.Instance = this;
MapAnchorMenu.Instance._canFade = false;
}
componentWillUnmount() {
+ this.destinationFeatures = [];
+ this.destinationSelected = false;
+ this.selectedDestinationFeature = undefined;
+ this.currentRouteInfoMap = undefined;
this._disposer?.();
}
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views().slice(),
+ () => SelectionManager.Views.slice(),
sel => MapAnchorMenu.Instance.fadeOut(true)
);
}
@@ -81,39 +162,292 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
};
static top = React.createRef<HTMLDivElement>();
+
// public get Top(){
// return this.top
// }
+ @action
+ DirectionsClick = () => {
+ this.menuType = 'routeCreation';
+ };
+
+ @action
+ CustomizeClick = () => {
+ this.currentRouteInfoMap = undefined;
+ this.menuType = 'customize';
+ };
+
+ @action
+ BackClick = () => {
+ this.currentRouteInfoMap = undefined;
+ this.menuType = 'standard';
+ };
+
+ @action
+ TriggerFileInputClick = () => {
+ if (this._fileInputRef) {
+ this._fileInputRef.current?.click(); // Trigger the file input click event
+ }
+ };
+
+ @action
+ onMarkerColorChange = (color: ColorResult) => {
+ if (this.pinDoc) {
+ this.pinDoc.markerColor = color.hex;
+ }
+ };
+
+ revertToOriginalMarker = () => {
+ if (this.pinDoc) {
+ this.pinDoc.markerType = 'MAP_PIN';
+ this.pinDoc.markerColor = '#ff5722';
+ }
+ };
+
+ onMarkerIconChange = (iconKey: string) => {
+ if (this.pinDoc) {
+ this.pinDoc.markerType = iconKey;
+ }
+ };
+
+ @observable
+ destinationFeatures: any[] = [];
+
+ @observable
+ destinationSelected: boolean = false;
+
+ @observable
+ selectedDestinationFeature: any = undefined;
+
+ @observable
+ createPinForDestination: boolean = true;
+
+ @observable
+ currentRouteInfoMap: Record<TransportationType, any> | undefined = undefined;
+
+ @observable
+ selectedTransportationType: TransportationType = 'driving';
+
+ @action
+ handleTransportationTypeChange = (newType: TransportationType) => {
+ if (newType !== this.selectedTransportationType) {
+ this.selectedTransportationType = newType;
+ this.DisplayRoute(this.currentRouteInfoMap, newType);
+ }
+ };
+
+ @action
+ handleSelectedDestinationFeature = (destinationFeature: any) => {
+ this.selectedDestinationFeature = destinationFeature;
+ };
+
+ @action
+ toggleCreatePinForDestinationCheckbox = () => {
+ this.createPinForDestination = !this.createPinForDestination;
+ };
+
+ @action
+ handleDestinationSearchChange = async (searchText: string) => {
+ if (this.selectedDestinationFeature !== undefined) this.selectedDestinationFeature = undefined;
+ const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
+ if (features) {
+ runInAction(() => {
+ this.destinationFeatures = features;
+ });
+ }
+ };
+
+ getRoutes = async (destinationFeature: any) => {
+ const currentPinLong: number = NumCast(this.pinDoc?.longitude);
+ const currentPinLat: number = NumCast(this.pinDoc?.latitude);
+
+ if (currentPinLong && currentPinLat && destinationFeature.center) {
+ const routeInfoMap = await MapboxApiUtility.getDirections([currentPinLong, currentPinLat], destinationFeature.center);
+ if (routeInfoMap) {
+ runInAction(() => {
+ this.currentRouteInfoMap = routeInfoMap;
+ });
+ this.DisplayRoute(routeInfoMap, 'driving');
+ }
+ }
+
+ // get route menu, set it equal to here
+ // create a temporary route
+ // create pin if createPinForDestination was clicked
+ };
+
+ HandleAddRouteClick = () => {
+ if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) {
+ const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+ console.log(coordinates);
+ console.log(this.selectedDestinationFeature);
+ this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
+ }
+ };
+
+ getMarkerIcon = (): JSX.Element | undefined => {
+ if (this.pinDoc) {
+ const markerType = StrCast(this.pinDoc.markerType);
+ const markerColor = StrCast(this.pinDoc.markerColor);
+
+ return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor);
+ }
+ return undefined;
+ };
+
+ 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}
+ />
+ )
+
+ }
+ 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>
+ )
+ }
+
+ linkNoteToPinOrRoutenButton: JSX.Element = (
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip="Link Note to Pin" //
+ onPointerDown={this.notePointerDown}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
+ )
+
+ customizePinButton: JSX.Element = (
+ <IconButton
+ tooltip="Customize pin"
+ onPointerDown={this.CustomizeClick}
+ icon={<FontAwesomeIcon icon={faEdit as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ centerOnPinButton: JSX.Element = (
+ <IconButton
+ tooltip="Center on pin" //
+ onPointerDown={this.Center}
+ icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ backButton: JSX.Element = (
+ <IconButton
+ tooltip="Go back" //
+ onPointerDown={this.BackClick}
+ icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ addRouteButton: JSX.Element = (
+ <IconButton
+ tooltip="Add route" //
+ onPointerDown={this.HandleAddRouteClick}
+ icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ getDeleteButton = (type: string) => {
+ return (
+ <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}
+ />
+ )
+
+ revertToOriginalMarkerButton = (
+ <IconButton
+ tooltip="Revert to original" //
+ onPointerDown={() => this.revertToOriginalMarker()}
+ icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
render() {
const buttons = (
- <>
- {
- <IconButton
- tooltip="Delete Pin" //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={SettingsManager.userColor}
- />
- }
- {
- <div ref={this._commentRef}>
- <IconButton
- tooltip="Link Note to Pin" //
- onPointerDown={this.notePointerDown}
- icon={<FontAwesomeIcon icon="sticky-note" />}
- color={SettingsManager.userColor}
- />
- </div>
- }
- {
- <IconButton
- tooltip="Center on pin" //
- onPointerDown={this.Center}
- icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
- color={SettingsManager.userColor}
- />
- }
+ <div className="menu-buttons" style={{ display: 'flex' }}>
+ {this.menuType === 'standard' && (
+ <>
+ {this.getDeleteButton('pin')}
+ {this.getDirectionsButton}
+ {this.getAddToCalendarButton('pin')}
+ {this.getLinkNoteToDocButton('pin')}
+ {this.customizePinButton}
+ {this.centerOnPinButton}
+ </>
+ )}
+ {this.menuType === 'routeCreation' && (
+ <>
+ {this.backButton}
+ {this.addRouteButton}
+ </>
+ )}
+ {this.menuType === 'route' && (
+ <>
+ {this.getDeleteButton('route')}
+ {this.animateRouteButton}
+ {this.getAddToCalendarButton('route')}
+ {this.getLinkNoteToDocButton('route')}
+ </>
+ )}
+ {this.menuType === 'customize' && (
+ <>
+ {this.backButton}
+ {this.revertToOriginalMarkerButton}
+ </>
+ )}
+
{/* {this.IsTargetToggler !== returnFalse && (
<Toggle
tooltip={'Make target visibility toggle on click'}
@@ -125,13 +459,100 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
)} */}
- </>
+ </div>
);
return this.getElement(
- <div ref={MapAnchorMenu.top} style={{ width: '100%', display: 'flex' }}>
+ <div ref={MapAnchorMenu.top} className="map-anchor-menu-container">
+ {this.menuType === 'standard' && <div>{this.title}</div>}
+ {this.menuType === 'routeCreation' && (
+ <div className="direction-inputs" style={{ display: 'flex', flexDirection: 'column' }}>
+ <TextField fullWidth disabled value={this.title} />
+ <FontAwesomeIcon icon={faArrowDown as IconLookup} size="xs" />
+ <Autocomplete
+ fullWidth
+ id="route-destination-searcher"
+ onInputChange={(e: any, searchText: any) => this.handleDestinationSearchChange(searchText)}
+ onChange={(e: any, feature: any, reason: any) => {
+ if (reason === 'clear') {
+ this.handleSelectedDestinationFeature(undefined);
+ } else if (reason === 'selectOption') {
+ this.handleSelectedDestinationFeature(feature);
+ }
+ }}
+ options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)}
+ getOptionLabel={(feature: any) => feature.place_name}
+ 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)}>
+ Get routes
+ </button>
+
+ {/* <input
+ placeholder="Origin"
+ /> */}
+ </div>
+ )}
+ {this.currentRouteInfoMap && (
+ <div className="current-route-info-container">
+ <div className="transportation-icons-container">
+ <IconButton
+ tooltip="Driving route"
+ onPointerDown={() => this.handleTransportationTypeChange('driving')}
+ icon={<FontAwesomeIcon icon={faCar as IconLookup} />}
+ color={this.selectedTransportationType === 'driving' ? 'lightblue' : 'grey'}
+ />
+ <IconButton
+ tooltip="Cycling route"
+ onPointerDown={() => this.handleTransportationTypeChange('cycling')}
+ icon={<FontAwesomeIcon icon={faBicycle as IconLookup} />}
+ color={this.selectedTransportationType === 'cycling' ? 'lightblue' : 'grey'}
+ />
+ <IconButton
+ tooltip="Walking route"
+ onPointerDown={() => this.handleTransportationTypeChange('walking')}
+ icon={<FontAwesomeIcon icon={faPersonWalking as IconLookup} />}
+ color={this.selectedTransportationType === 'walking' ? 'lightblue' : 'grey'}
+ />
+ </div>
+ <div className="selected-route-details-container">
+ <div>Duration: {this.currentRouteInfoMap[this.selectedTransportationType].duration}</div>
+ <div>Distance: {this.currentRouteInfoMap[this.selectedTransportationType].distance}</div>
+ </div>
+ </div>
+ )}
+ {this.menuType === 'customize' && (
+ <div className="customized-marker-container">
+ <div className="current-marker-container">
+ <div>Current Marker: </div>
+ <div>{this.getMarkerIcon()}</div>
+ </div>
+ <div className="color-picker-container" style={{ marginBottom: '10px' }}>
+ <CirclePicker circleSize={15} circleSpacing={7} width="100%" onChange={color => this.onMarkerColorChange(color)} />
+ </div>
+ <div className="all-markers-container">
+ {Object.keys(MarkerIcons.FAMarkerIconsMap).map(iconKey => (
+ <div key={iconKey} className="marker-icon">
+ <IconButton onPointerDown={() => this.onMarkerIconChange(iconKey)} icon={MarkerIcons.getFontAwesomeIcon(iconKey, '1x', 'white')} />
+ </div>
+ ))}
+ </div>
+ <div style={{ width: '100%', height: '3px', color: 'white' }}></div>
+ </div>
+ )}
+ {this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>}
{buttons}
- </div>
+ </div>,
+ true
);
}
}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 242677231..25b4587a5 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -1,9 +1,17 @@
-@import '../../global/globalCssVariables.scss';
+@import '../../global/globalCssVariables.module.scss';
.mapBox {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
+ position: absolute;
+
+ .mapboxgl-map {
+ overflow: unset !important;
+ }
+ .mapboxgl-ctrl {
+ display: none !important;
+ }
.mapBox-infoWindow {
background-color: white;
@@ -14,15 +22,133 @@
.mapBox-searchbar {
display: flex;
flex-direction: row;
+ gap: 5px;
+ align-items: center;
width: calc(100% - 40px);
- .editableText-container {
+
+ // .editableText-container {
+ // width: 100%;
+ // font-size: 16px !important;
+ // }
+ // input {
+ // width: 100%;
+ // }
+ }
+
+ .mapbox-settings-panel {
+ z-index: 900;
+ padding: 10px 20px;
+ display: flex;
+ background-color: rgb(187, 187, 187);
+ font-size: 1.3em;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 7px;
+ position: absolute;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+
+ .mapbox-style-select {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 4px;
+ }
+
+ .mapbox-terrain-selection {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 4px;
+ }
+ }
+
+ .mapbox-geocoding-search-results {
+ z-index: 900;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ position: absolute;
+ background-color: rgb(187, 187, 187);
+ font-size: 1.4em;
+ padding: 10px;
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px;
+
+ .search-result-container {
width: 100%;
- font-size: 16px !important;
+ padding: 10px;
+ &:hover {
+ background-color: lighten(rgb(187, 187, 187), 10%);
+ }
}
- input {
+ }
+
+ .animation-panel {
+ z-index: 900;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ background-color: rgb(187, 187, 187);
+ padding: 10px;
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px;
+ position: absolute;
+
+ #route-to-animate-title {
+ font-size: 1.25em;
+ font-weight: bold;
+ }
+
+ .route-animation-options {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
width: 100%;
+
+ .animation-suboptions {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 7px;
+ width: 100%;
+
+ label {
+ margin-bottom: 0;
+ }
+
+ .speed-label {
+ margin-right: 5px;
+ }
+
+ #divider {
+ margin-left: 10px;
+ margin-right: 10px;
+ }
+ }
}
}
+
+ .zoom-box {
+ position: absolute;
+ z-index: 900;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: white;
+ font-size: 1.4em;
+ border-radius: 5px;
+ bottom: 5px;
+ left: 5px;
+ padding: 3px;
+ }
+
.mapBox-topbar {
display: flex;
flex-direction: row;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 9b75ca7e3..7db139d74 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,32 +1,44 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import BingMapsReact from 'bingmaps-react';
-import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { IconButton, Size, Type } from 'browndash-components';
+import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } 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 { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
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 { undoable, UndoManager } from '../../../util/UndoManager';
-import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { UndoManager, undoable } from '../../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
-import { Colors } from '../../global/globalEnums';
import { SidebarAnnos } from '../../SidebarAnnos';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { MapAnchorMenu } from './MapAnchorMenu';
+import { ControlPosition, Layer, MapProvider, MapRef, Map as MapboxMap, Marker, MarkerProps, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
import './MapBox.scss';
+// import { GeocoderControl } from './GeocoderControl';
+import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
+import { Checkbox, FormControlLabel, TextField } from '@mui/material';
+import * as turf from '@turf/turf';
+import * as d3 from 'd3';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
+import { CirclePicker, ColorResult } from 'react-color';
+import { MarkerEvent } from 'react-map-gl/dist/esm/types';
+import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
+import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
+import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
+import { MarkerIcons } from './MarkerIcons';
+
// amongus
/**
* MapBox architecture:
@@ -42,6 +54,30 @@ import './MapBox.scss';
*/
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>
+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 = {
+ longitude: number;
+ latitude: number;
+ title: string;
+ description: string;
+};
+
+// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+// mapboxAccessToken: string;
+// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+// position: ControlPosition;
+
+// onResult: (...args: any[]) => void;
+// };
+
+type MapMarker = {
+ longitude: number;
+ latitude: number;
+};
/**
* Consider integrating later: allows for drawing, circling, making shapes on map
@@ -67,9 +103,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _dragRef = React.createRef<HTMLDivElement>();
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+ private _mapRef: React.RefObject<MapRef> = React.createRef();
private _disposers: { [key: string]: IReactionDisposer } = {};
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get allSidebarDocs() {
return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -81,6 +123,116 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get allPushpins() {
return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
}
+ @computed get allRoutes() {
+ return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+ }
+ @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
+ if (this.routeToAnimate?.routeCoordinates) {
+ const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+ // const index = Math.floor(this.animationPhase * originalCoordinates.length);
+ const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
+ console.log('Animation phase', this.animationPhase);
+ const startIndex = Math.floor(index);
+ const endIndex = Math.ceil(index);
+ let feature: Feature<Geometry, GeoJsonProperties>;
+
+ let geometry: LineString;
+ if (startIndex === endIndex) {
+ // AnimationPhase is at a whole number (no interpolation needed)
+ const coordinates = [originalCoordinates[startIndex]];
+ geometry = {
+ type: 'LineString',
+ coordinates,
+ };
+ feature = {
+ type: 'Feature',
+ properties: {
+ routeTitle: StrCast(this.routeToAnimate.title),
+ },
+ geometry: geometry,
+ };
+ } else {
+ // Interpolate between two coordinates
+ const startCoord = originalCoordinates[startIndex];
+ const endCoord = originalCoordinates[endIndex];
+ const fraction = index - startIndex;
+
+ const interpolator = d3.interpolateArray(startCoord, endCoord);
+
+ const interpolatedCoord = interpolator(fraction);
+
+ const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
+
+ geometry = {
+ type: 'LineString',
+ coordinates,
+ };
+ feature = {
+ type: 'Feature',
+ properties: {
+ routeTitle: StrCast(this.routeToAnimate.title),
+ },
+ geometry: geometry,
+ };
+ }
+
+ autorun(() => {
+ const animationUtil = this.animationUtility;
+ const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
+ const newFeature: Feature<LineString, turf.Properties> = {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: concattedCoordinates,
+ },
+ };
+ if (animationUtil) {
+ animationUtil.setPath(newFeature);
+ }
+ });
+ return feature;
+ }
+ console.log('ERROR');
+ return {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: [],
+ },
+ };
+ }
+ @computed get selectedRouteCoordinates(): Position[] {
+ let coordinates: Position[] = [];
+ if (this.routeToAnimate?.routeCoordinates) {
+ coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+ }
+ return coordinates;
+ }
+
+ @computed get allRoutesGeoJson(): FeatureCollection {
+ const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map((routeDoc: Doc) => {
+ console.log('Route coords: ', routeDoc.routeCoordinates);
+ const geometry: LineString = {
+ type: 'LineString',
+ coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
+ };
+ return {
+ type: 'Feature',
+ properties: {
+ routeTitle: routeDoc.title,
+ },
+ geometry: geometry,
+ };
+ });
+
+ return {
+ type: 'FeatureCollection',
+ features: features,
+ };
+ }
+
@computed get SidebarShown() {
return this.layoutDoc._layout_showSidebar ? true : false;
}
@@ -88,7 +240,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
}
@computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@computed get SidebarKey() {
return this.fieldKey + '_sidebar';
@@ -96,13 +248,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentDidMount() {
this._unmounting = false;
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
_unmounting = false;
componentWillUnmount(): void {
this._unmounting = true;
- this.deselectPin();
+ this.deselectPinOrRoute();
this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
}
@@ -117,7 +269,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
- let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
+ let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute;
if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
}
@@ -161,9 +313,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e,
(e, down, delta) =>
runInAction(() => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const fullWidth = NumCast(this.layoutDoc._width);
const mapWidth = fullWidth - this.sidebarWidth();
@@ -182,7 +334,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
() => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
);
};
- sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
/**
* Handles toggle of sidebar on click the little comment button
@@ -194,8 +346,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
- top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+ display: !this._props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
@@ -218,25 +370,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const sourceAnchorCreator = action(() => {
const note = this.getAnchor(true);
- if (note && this.selectedPin) {
- note.latitude = this.selectedPin.latitude;
- note.longitude = this.selectedPin.longitude;
- note.map = this.selectedPin.map;
+ if (note && this.selectedPinOrRoute) {
+ note.latitude = this.selectedPinOrRoute.latitude;
+ note.longitude = this.selectedPinOrRoute.longitude;
+ note.map = this.selectedPinOrRoute.map;
}
return note as Doc;
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
- FormattedTextBox.SelectOnLoad = target[Id];
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ FormattedTextBox.SetSelectOnLoad(target);
return target;
};
- const docView = this.props.DocumentView?.();
+ const docView = this._props.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.props.Document;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -247,10 +399,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const createFunc = undoable(
action(() => {
const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
- if (note && this.selectedPin) {
- note.latitude = this.selectedPin.latitude;
- note.longitude = this.selectedPin.longitude;
- note.map = this.selectedPin.map;
+ if (note && this.selectedPinOrRoute) {
+ note.latitude = this.selectedPinOrRoute.latitude;
+ note.longitude = this.selectedPinOrRoute.longitude;
+ note.map = this.selectedPinOrRoute.map;
}
}),
'create note annotation'
@@ -275,15 +427,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
- pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+ pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
- panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
- panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
- infoWidth = () => this.props.PanelWidth() / 5;
- infoHeight = () => this.props.PanelHeight() / 5;
+ panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+ panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+ scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+ transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ infoWidth = () => this._props.PanelWidth() / 5;
+ infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
savedAnnotations = () => this._savedAnnotations;
@@ -321,55 +473,34 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@observable
- bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string)
+ bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
geoDataRequestOptions = {
entityType: 'PopulatedPlace',
};
- // incrementer: number = 0;
- /*
- * Creates Pushpin doc and adds it to the list of annotations
- */
- @action
- createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
- // Stores the pushpin as a MapMarkerDocument
- const pushpin = Docs.Create.PushpinDocument(
- NumCast(latitude),
- NumCast(longitude),
- false,
- [],
- { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
- // ,'pushpinIDamongus'+ this.incrementer++
- );
- this.addDocument(pushpin, this.annotationKey);
- return pushpin;
- // mapMarker.infoWindowOpen = true;
- }, 'createpin');
-
// The pin that is selected
- @observable selectedPin: Doc | undefined;
+ @observable selectedPinOrRoute: Doc | undefined;
@action
- deselectPin = () => {
- if (this.selectedPin) {
- // Removes filter
- Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
- Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
-
- const temp = this.selectedPin;
- if (!this._unmounting) {
- 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));
- if (!this._unmounting) {
- this._bingMap.current.entities.push(newpin);
- }
- this.map_docToPinMap.set(temp, newpin);
- this.selectedPin = undefined;
- this.bingSearchBarContents = this.rootDoc.map;
+ deselectPinOrRoute = () => {
+ if (this.selectedPinOrRoute) {
+ // // Removes filter
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+ // Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ // const temp = this.selectedPin;
+ // if (!this._unmounting) {
+ // 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));
+ // if (!this._unmounting) {
+ // this._bingMap.current.entities.push(newpin);
+ // }
+ // this.map_docToPinMap.set(temp, newpin);
+ // this.selectedPin = undefined;
+ // this.bingSearchBarContents = this.Document.map;
}
};
@@ -382,25 +513,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
*/
@action
pushpinClicked = (pinDoc: Doc) => {
- this.deselectPin();
- this.selectedPin = pinDoc;
+ this.deselectPinOrRoute();
+ this.selectedPinOrRoute = pinDoc;
this.bingSearchBarContents = pinDoc.map;
- // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
- // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
- this.recolorPin(this.selectedPin, 'green');
+ this.recolorPin(this.selectedPinOrRoute, 'green');
- MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
- const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
- const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = point.y + this.props.PanelHeight() / 2 + 32;
- const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
+ const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = point.y + this._props.PanelHeight() / 2 + 32;
+ const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -411,8 +542,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
*/
@action
mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
- this.props.select(false);
- this.deselectPin();
+ this._props.select(false);
+ this.deselectPinOrRoute();
};
/*
* Updates values of layout doc to match the current map
@@ -457,24 +588,24 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
- title: 'MapAnchor:' + this.rootDoc.title,
- text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location',
- config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
- config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
+ title: 'MapAnchor:' + this.Document.title,
+ text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+ config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
+ config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
- config_map_type: StrCast(this.dataDoc.map_type),
- config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
+ // config_map_type: StrCast(this.dataDoc.map_type),
+ config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
layout_unrendered: true,
- mapPin: existingPin ?? this.selectedPin,
- annotationOn: this.rootDoc,
+ mapPin: existingPin ?? this.selectedPinOrRoute,
+ annotationOn: this.Document,
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
return anchor;
}
- return this.rootDoc;
+ return this.Document;
};
map_docToPinMap = new Map<Doc, any>();
@@ -504,7 +635,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* Removes pin from annotations
*/
@action
- removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
+ removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);
/*
* Removes pushpin from map render
@@ -514,18 +645,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
}
this.map_docToPinMap.delete(pinDoc);
- this.selectedPin = undefined;
+ this.selectedPinOrRoute = undefined;
};
@action
- deleteSelectedPin = undoable(() => {
- if (this.selectedPin) {
+ deleteSelectedPinOrRoute = undoable(() => {
+ console.log('deleting')
+ if (this.selectedPinOrRoute) {
// Removes filter
- Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
- Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
+ Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
- this.removePushpin(this.selectedPin);
+ this.removePushpinOrRoute(this.selectedPinOrRoute);
}
MapAnchorMenu.Instance.fadeOut(true);
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -534,23 +666,39 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
tryHideMapAnchorMenu = (e: PointerEvent) => {
let target = document.elementFromPoint(e.x, e.y);
while (target) {
+ if (target.id === 'route-destination-searcher-listbox') return;
if (target === MapAnchorMenu.top.current) return;
target = target.parentElement;
}
e.stopPropagation();
e.preventDefault();
MapAnchorMenu.Instance.fadeOut(true);
+ runInAction(() => {
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: [],
+ }
+ })
+
+
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
};
+
+
@action
centerOnSelectedPin = () => {
- if (this.selectedPin) {
- this.dataDoc.latitude = this.selectedPin.latitude;
- this.dataDoc.longitude = this.selectedPin.longitude;
- this.dataDoc.map = this.selectedPin.map ?? '';
- this.bingSearchBarContents = this.selectedPin.map;
+ if (this.selectedPinOrRoute) {
+ this._mapRef.current?.flyTo({
+ center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)],
+ });
}
+ // if (this.selectedPin) {
+ // this.dataDoc.latitude = this.selectedPin.latitude;
+ // this.dataDoc.longitude = this.selectedPin.longitude;
+ // this.dataDoc.map = this.selectedPin.map ?? '';
+ // this.bingSearchBarContents = this.selectedPin.map;
+ // }
MapAnchorMenu.Instance.fadeOut(true);
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
};
@@ -582,16 +730,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
},
};
- @action
- 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._bingMap.current.entities.push(newpin);
- this.map_docToPinMap.set(pin, newpin);
+ // 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._bingMap.current.entities.push(newpin);
+ // this.map_docToPinMap.set(pin, newpin);
};
/*
@@ -611,7 +756,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
this._disposers.mapLocation = reaction(
- () => this.rootDoc.map,
+ () => this.Document.map,
mapLoc => (this.bingSearchBarContents = mapLoc),
{ fireImmediately: true }
);
@@ -636,7 +781,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
this._disposers.location = reaction(
- () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }),
+ () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
locationObject => {
// if (this._bingMap.current)
try {
@@ -661,24 +806,26 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e,
e,
e => {
+ // move event
if (!dragClone) {
- dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
+ dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin
dragClone.style.position = 'absolute';
dragClone.style.zIndex = '10000';
- DragManager.Root().appendChild(dragClone);
+ DragManager.Root().appendChild(dragClone); // add clone to root
}
dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
return false;
},
e => {
+ // up event
if (!dragClone) return;
DragManager.Root().removeChild(dragClone);
- let target = document.elementFromPoint(e.x, e.y);
+ let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
while (target) {
if (target === this._ref.current) {
- const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.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 cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
this.createPushpin(location.latitude, location.longitude);
break;
@@ -687,7 +834,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
},
e => {
- const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map);
+ const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
if (this.bingSearchBarContents) {
this.bingSearch().then(createPin);
} else createPin();
@@ -695,15 +842,712 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
};
+ // incrementer: number = 0;
+ /*
+ * Creates Pushpin doc and adds it to the list of annotations
+ */
+ @action
+ createPushpin = undoable((latitude: number, longitude: number, location?: string, wikiData?: string) => {
+ // Stores the pushpin as a MapMarkerDocument
+ const pushpin = Docs.Create.PushpinDocument(
+ NumCast(latitude),
+ NumCast(longitude),
+ false,
+ [],
+ {
+ title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`,
+ map: location,
+ description: '',
+ wikiData: wikiData,
+ markerType: 'MAP_PIN',
+ markerColor: '#ff5722',
+ }
+ // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
+ // ,'pushpinIDamongus'+ this.incrementer++
+ );
+ this.addDocument(pushpin, this.annotationKey);
+ console.log(pushpin);
+ return pushpin;
+
+ // mapMarker.infoWindowOpen = true;
+ }, 'createpin');
+
+ @action
+ createMapRoute = undoable((coordinates: Position[], originName: string, destination: any, createPinForDestination: boolean) => {
+ if (originName !== destination.place_name) {
+ const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${originName} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) });
+ this.addDocument(mapRoute, this.annotationKey);
+ if (createPinForDestination) {
+ this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
+ }
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: []
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
+ return mapRoute;
+ }
+ // TODO: Display error that can't create route to same location
+ }, 'createmaproute');
+
searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
+ @observable
+ featuresFromGeocodeResults: any[] = [];
+
+ @action
+ addMarkerForFeature = (feature: any) => {
+ const location = feature.place_name;
+ if (feature.center) {
+ const longitude = feature.center[0];
+ const latitude = feature.center[1];
+ const wikiData = feature.properties?.wikiData;
+
+ this.createPushpin(latitude, longitude, location, wikiData);
+
+ if (this._mapRef.current) {
+ this._mapRef.current.flyTo({
+ center: feature.center,
+ });
+ }
+ this.featuresFromGeocodeResults = [];
+ } else {
+ // TODO: handle error
+ }
+ };
+
+ /**
+ * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input
+ * @param searchText the search input (presumably a location)
+ */
+ handleSearchChange = async (searchText: string) => {
+ const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
+ if (features && !this.isAnimating) {
+ runInAction(() => {
+ this.settingsOpen = false;
+ this.featuresFromGeocodeResults = features;
+ this.routeToAnimate = undefined;
+ });
+ }
+ // try {
+ // 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();
+ // runInAction(() => {
+ // this.featuresFromGeocodeResults = data.features;
+ // })
+ // } catch (error: any){
+ // // TODO: handle error in better way
+ // console.log(error);
+ // }
+ };
+ // @action
+ // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);
+
+ @action
+ handleMapClick = (e: MapLayerMouseEvent) => {
+ this.featuresFromGeocodeResults = [];
+ this.settingsOpen = false;
+ if (this._mapRef.current) {
+ const features = this._mapRef.current.queryRenderedFeatures(e.point, {
+ layers: ['map-routes-layer'],
+ });
+
+ 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 routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
+ this.deselectPinOrRoute(); // TODO: Also deselect route if selected
+ if (routeDoc) {
+ this.selectedPinOrRoute = routeDoc;
+ Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+
+ // TODO: Recolor route
+
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
+ MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+ MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+ MapAnchorMenu.Instance.Reset();
+
+ MapAnchorMenu.Instance.setRouteDoc(routeDoc);
+
+ // TODO: Subject to change
+ MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
+
+ MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
+ MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
+ MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
+ MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel;
+
+ // this.selectedRouteCoordinates = geometry.coordinates;
+
+ MapAnchorMenu.Instance.setMenuType('route');
+
+ MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
+
+ document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ }
+ }
+ }
+ };
+
+
+
+ /**
+ * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
+ * and latitude). Sets the search results accordingly.
+ * @param e
+ */
+ handleMapDblClick = async (e: MapLayerMouseEvent) => {
+ e.preventDefault();
+ const lngLat: LngLat = e.lngLat;
+ const longitude: number = lngLat.lng;
+ const latitude: number = lngLat.lat;
+
+ const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
+ if (features) {
+ runInAction(() => {
+ this.featuresFromGeocodeResults = features;
+ });
+ }
+
+ // // REVERSE GEOCODE TO GET LOCATION DETAILS
+ // try {
+ // 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();
+ // console.log("REV GEOCODE DATA: ", data);
+ // runInAction(() => {
+ // this.featuresFromGeocodeResults = data.features;
+ // })
+ // } catch (error: any){
+ // // TODO: handle error in better way
+ // console.log(error);
+ // }
+ };
+
+ @observable
+ currentPopup: PopupInfo | undefined = undefined;
+
+ @action
+ handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
+ this.featuresFromGeocodeResults = [];
+ this.deselectPinOrRoute(); // TODO: check this method
+ this.selectedPinOrRoute = pinDoc;
+ // this.bingSearchBarContents = pinDoc.map;
+
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+
+ this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
+
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
+ MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+ MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+ MapAnchorMenu.Instance.Reset();
+
+ // pass in the pinDoc
+ MapAnchorMenu.Instance.setPinDoc(pinDoc);
+ MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
+
+ MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
+ MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
+ MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
+
+ MapAnchorMenu.Instance.setMenuType('standard');
+
+ // MapAnchorMenu.Instance.jumpTo(NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3, true);
+
+ MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
+
+ document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+
+ // this._mapRef.current.flyTo({
+ // center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3]
+ // })
+ };
+
+ @observable
+ temporaryRouteSource: FeatureCollection = {
+ type: 'FeatureCollection',
+ features: [],
+ };
+
+ @action
+ displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
+ if (routeInfoMap) {
+ const newTempRouteSource: FeatureCollection = {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: routeInfoMap[type].coordinates,
+ },
+ },
+ ],
+ };
+ // TODO: Create pin for destination
+ // TODO: Fly to point where full route will be shown
+ this.temporaryRouteSource = newTempRouteSource;
+ }
+ };
+
+ @observable
+ isAnimating: boolean = false;
+
+ @observable
+ routeToAnimate: Doc | undefined = undefined;
+
+ @observable
+ animationPhase: number = 0;
+
+ @observable
+ finishedFlyTo: boolean = false;
+
+ @action
+ setAnimationPhase = (newValue: number) => {
+ this.animationPhase = newValue;
+ };
+
+ @observable
+ frameId: number | null = null;
+
+ @action
+ setFrameId = (frameId: number) => {
+ this.frameId = frameId;
+ };
+
+ @observable
+ animationUtility: AnimationUtility | null = null;
+
+ @action
+ setAnimationUtility = (util: AnimationUtility) => {
+ this.animationUtility = util;
+ };
+
+ @action
+ openAnimationPanel = (routeDoc: Doc | undefined) => {
+ if (routeDoc) {
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ this.featuresFromGeocodeResults = [];
+ this.routeToAnimate = routeDoc;
+ }
+ };
+
+ @computed get mapboxMapViewState(): ViewState {
+ return {
+ zoom: NumCast(this.dataDoc.map_zoom, 8),
+ longitude: NumCast(this.dataDoc.longitude, -71.4128),
+ latitude: NumCast(this.dataDoc.latitude, 41.824),
+ pitch: NumCast(this.dataDoc.map_pitch),
+ bearing: NumCast(this.dataDoc.map_bearing),
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ };
+ }
+
+ @computed
+ get preAnimationViewState() {
+ if (!this.isAnimating) {
+ return this.mapboxMapViewState;
+ }
+ }
+
+ @observable
+ isStreetViewAnimation: boolean = false;
+
+ @observable
+ animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+
+ @observable
+ animationLineColor: string = '#ffff00';
+
+ @action
+ setAnimationLineColor = (color: ColorResult) => {
+ this.animationLineColor = color.hex;
+ };
+
+ @action
+ updateAnimationSpeed = () => {
+ let newAnimationSpeed: AnimationSpeed;
+ switch (this.animationSpeed) {
+ case AnimationSpeed.SLOW:
+ newAnimationSpeed = AnimationSpeed.MEDIUM;
+ break;
+ case AnimationSpeed.MEDIUM:
+ newAnimationSpeed = AnimationSpeed.FAST;
+ break;
+ case AnimationSpeed.FAST:
+ newAnimationSpeed = AnimationSpeed.SLOW;
+ break;
+ default:
+ newAnimationSpeed = AnimationSpeed.MEDIUM;
+ break;
+ }
+ this.animationSpeed = newAnimationSpeed;
+ if (this.animationUtility) {
+ this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
+ }
+ };
+ @computed get animationSpeedTooltipText(): string {
+ switch (this.animationSpeed) {
+ case AnimationSpeed.SLOW:
+ return '1x speed';
+ case AnimationSpeed.MEDIUM:
+ return '2x speed';
+ case AnimationSpeed.FAST:
+ return '3x speed';
+ default:
+ return '2x speed';
+ }
+ }
+ @computed get animationSpeedIcon(): JSX.Element {
+ switch (this.animationSpeed) {
+ case AnimationSpeed.SLOW:
+ return slowSpeedIcon;
+ case AnimationSpeed.MEDIUM:
+ return mediumSpeedIcon;
+ case AnimationSpeed.FAST:
+ return fastSpeedIcon;
+ default:
+ return mediumSpeedIcon;
+ }
+ }
+
+ @action
+ toggleIsStreetViewAnimation = () => {
+ const newVal = !this.isStreetViewAnimation;
+ this.isStreetViewAnimation = newVal;
+ if (this.animationUtility) {
+ this.animationUtility.updateIsStreetViewAnimation(newVal);
+ }
+ };
+
+ @observable
+ dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: [],
+ },
+ };
+
+ @observable
+ path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
+ type: 'Feature',
+ geometry: {
+ type: 'LineString',
+ coordinates: [],
+ },
+ properties: {},
+ };
+
+ getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
+ const geometry: LineString = {
+ type: 'LineString',
+ coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
+ };
+ return {
+ type: 'Feature',
+ properties: {
+ routeTitle: routeDoc.title,
+ },
+ geometry: geometry,
+ };
+ };
+
+ @action
+ playAnimation = (status: AnimationStatus) => {
+ if (!this._mapRef.current || !this.routeToAnimate) {
+ return;
+ }
+
+ this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
+ this.frameId = AnimationStatus.RESUME ? this.frameId : null;
+ this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
+
+ const path = turf.lineString(this.selectedRouteCoordinates);
+
+ this.settingsOpen = false;
+ this.path = path;
+ this.isAnimating = true;
+
+ runInAction(() => {
+ return new Promise<void>(async resolve => {
+ const targetLngLat = {
+ lng: this.selectedRouteCoordinates[0][0],
+ lat: this.selectedRouteCoordinates[0][1],
+ };
+
+ const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current);
+ runInAction(() => {
+ this.setAnimationUtility(animationUtil);
+ });
+
+ const updateFrameId = (newFrameId: number) => {
+ this.setFrameId(newFrameId);
+ };
+
+ const updateAnimationPhase = (newAnimationPhase: number) => {
+ this.setAnimationPhase(newAnimationPhase);
+ };
+
+ if (status !== AnimationStatus.RESUME) {
+ const result = await animationUtil.flyInAndRotate({
+ map: this._mapRef.current!,
+ // targetLngLat,
+ // duration 3000
+ // startAltitude: 3000000,
+ // endAltitude: this.isStreetViewAnimation ? 80 : 12000,
+ // startBearing: 0,
+ // endBearing: -20,
+ // startPitch: 40,
+ // endPitch: this.isStreetViewAnimation ? 80 : 50,
+ updateFrameId,
+ });
+
+ console.log('Bearing: ', result.bearing);
+ console.log('Altitude: ', result.altitude);
+ }
+
+ runInAction(() => {
+ this.finishedFlyTo = true;
+ });
+
+ // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
+ await animationUtil.animatePath({
+ map: this._mapRef.current!,
+ // path: this.path,
+ // startBearing: -20,
+ // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
+ // pitch: this.isStreetViewAnimation ? 80: 50,
+ currentAnimationPhase: this.animationPhase,
+ updateAnimationPhase,
+ updateFrameId,
+ });
+
+ // get the bounds of the linestring, use fitBounds() to animate to a final view
+ const bbox3d = turf.bbox(this.path);
+
+ const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]];
+
+ this._mapRef.current!.fitBounds(bbox2d, {
+ duration: 3000,
+ pitch: 30,
+ bearing: 0,
+ padding: 120,
+ });
+
+ setTimeout(() => {
+ this.isStreetViewAnimation = false;
+ resolve();
+ }, 10000);
+ });
+ });
+ };
+
+ @action
+ pauseAnimation = () => {
+ if (this.frameId && this.animationPhase > 0) {
+ window.cancelAnimationFrame(this.frameId);
+ this.frameId = null;
+ this.isAnimating = false;
+ }
+ };
+
+ @action
+ stopAnimation = (close: boolean) => {
+ if (this.frameId) {
+ window.cancelAnimationFrame(this.frameId);
+ }
+ this.animationPhase = 0;
+ this.frameId = null;
+ this.finishedFlyTo = false;
+ this.isAnimating = false;
+ if (close) {
+ this.animationSpeed = AnimationSpeed.MEDIUM;
+ this.isStreetViewAnimation = false;
+ this.routeToAnimate = undefined;
+ this.animationUtility = null;
+ }
+ };
+
+
+ getRouteAnimationOptions = (): JSX.Element => {
+ return (
+ <>
+ <IconButton
+ tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+ onPointerDown={() => {
+ if (this.isAnimating && this.finishedFlyTo) {
+ this.pauseAnimation();
+ } else if (this.animationPhase > 0) {
+ this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase
+ } else {
+ this.playAnimation(AnimationStatus.START); // Play from the beginning
+ }
+ }}
+ icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+ color="black"
+ size={Size.MEDIUM}
+ />
+ {this.isAnimating && this.finishedFlyTo && (
+ <IconButton
+ tooltip="Restart animation"
+ onPointerDown={() => {
+ this.stopAnimation(false);
+ this.playAnimation(AnimationStatus.START);
+ }}
+ icon={<FontAwesomeIcon icon={faRotate as IconLookup} />}
+ color="black"
+ size={Size.MEDIUM}
+ />
+ )}
+ <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} />
+ <>
+ <div className="animation-suboptions">
+ <div>|</div>
+ <FormControlLabel
+ className="first-person-label"
+ label="1st person animation:"
+ labelPlacement="start"
+ control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
+ />
+ <div id="divider">|</div>
+ <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
+ <div id="divider">|</div>
+ <div style={{display: 'flex', alignItems: 'center'}}>
+ <div>Select Line Color: </div>
+ <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color:any) => this.setAnimationLineColor(color)} />
+ </div>
+
+ </div>
+ </>
+ </>
+ );
+ };
+
+ @action
+ hideRoute = () => {
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: [],
+ };
+ };
+
+ @observable
+ settingsOpen: boolean = false;
+
+ @observable
+ mapStyle: string = 'mapbox://styles/mapbox/standard';
+
+ @observable
+ showTerrain: boolean = true;
+
+ @action
+ toggleSettings = () => {
+ if (!this.isAnimating && this.animationPhase == 0) {
+ this.featuresFromGeocodeResults = [];
+ this.settingsOpen = !this.settingsOpen;
+ }
+ };
+
+ @action
+ changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
+ this.dataDoc.map_style = e.target.value;
+ // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
+ };
+
+ @action
+ onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const bearing = parseInt(e.target.value);
+ if (!isNaN(bearing) && this._mapRef.current) {
+ console.log('bearing change')
+ const fixedBearing = Math.max(0, Math.min(360, bearing));
+ this._mapRef.current.setBearing(fixedBearing);
+ this.dataDoc.map_bearing = fixedBearing;
+ }
+ };
+
+ @action
+ onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const pitch = parseInt(e.target.value);
+ if (!isNaN(pitch) && this._mapRef.current) {
+ console.log('pitch change')
+ const fixedPitch = Math.max(0, Math.min(85, pitch));
+ this._mapRef.current.setPitch(fixedPitch);
+ this.dataDoc.map_pitch = fixedPitch;
+ }
+ };
+
+ @action
+ onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const zoom = parseInt(e.target.value);
+ if (!isNaN(zoom) && this._mapRef.current) {
+ const fixedZoom = Math.max(0, Math.min(16, zoom));
+ this._mapRef.current.setZoom(fixedZoom);
+ this.dataDoc.map_zoom = fixedZoom;
+ }
+ };
+
+ @action
+ onStepZoomChange = (increment: boolean) => {
+ if (this._mapRef.current) {
+ let newZoom: number;
+ if (increment) {
+ console.log('inc');
+ newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1);
+ } else {
+ console.log('dec');
+ newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1);
+ }
+ this._mapRef.current.setZoom(newZoom);
+ this.dataDoc.map_zoom = newZoom;
+ }
+ };
+
+ @action
+ onMapZoom = (e: ViewStateChangeEvent) => (this.dataDoc.map_zoom = e.viewState.zoom);
+
+ @action
+ onMapMove = (e: ViewStateChangeEvent) => {
+ this.dataDoc.longitude = e.viewState.longitude;
+ this.dataDoc.latitude = e.viewState.latitude;
+ };
+
+ @action
+ toggleShowTerrain = () => (this.showTerrain = !this.showTerrain);
+
+ getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
+ const markerType = StrCast(pinDoc.markerType);
+ const markerColor = StrCast(pinDoc.markerColor);
+
+ return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
+ };
+
static _firstRender = true;
static _rerenderDelay = 500;
_rerenderTimeout: any;
render() {
// bcz: no idea what's going on here, but bings maps have some kind of bug
// such that we need to delay rendering a second map on startup until the first map is rendered.
- this.rootDoc[DocCss];
+ this.Document[DocCss];
if (MapBox._rerenderDelay) {
// prettier-ignore
this._rerenderTimeout = this._rerenderTimeout ??
@@ -712,10 +1556,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
MapBox._rerenderDelay = 0;
}
this._rerenderTimeout = undefined;
- this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
+ this.Document[DocCss] = this.Document[DocCss] + 1;
}), MapBox._rerenderDelay);
return null;
}
+ const scale = this._props.NativeDimScaling?.() || 1;
+ const parscale = scale === 1 ? 1 : this._props.ScreenToLocalTransform().Scale ?? 1;
const renderAnnotations = (childFilters?: () => string[]) => null;
return (
@@ -726,93 +1572,188 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onPointerDown={async e => {
e.button === 0 && !e.ctrlKey && e.stopPropagation();
}}
- style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+ style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
<div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
{renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-
- <div className="mapBox-searchbar">
- <EditableText
- // editing
- setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
- onEnter={e => this.bingSearch()}
- placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
- textAlign="center"
- />
- <IconButton
- icon={
- <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>
- </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'} />} />
+ {SnappingManager.IsDragging ? null : renderAnnotations()}
+ {!this.routeToAnimate && (
+ <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
+ <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
</div>
- </div>
+ )}
+ {this.settingsOpen && !this.routeToAnimate && (
+ <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
+ <div className="mapbox-style-select">
+ <div>Map Style:</div>
+ <div>
+ <select onChange={this.changeMapStyle} value={StrCast(this.dataDoc.map_style)}>
+ <option value="mapbox://styles/mapbox/standard">Standard</option>
+ <option value="mapbox://styles/mapbox/streets-v11">Streets</option>
+ <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
+ <option value="mapbox://styles/mapbox/light-v11">Light</option>
+ <option value="mapbox://styles/mapbox/dark-v11">Dark</option>
+ <option value="mapbox://styles/mapbox/satellite-v9">Satellite</option>
+ <option value="mapbox://styles/mapbox/satellite-streets-v12">Satellite Streets</option>
+ <option value="mapbox://styles/mapbox/navigation-day-v1">Navigation Day</option>
+ <option value="mapbox://styles/mapbox/navigation-night-v1">Navigation Night</option>
+ </select>
+ </div>
+ </div>
+ <div className="mapbox-bearing-selection">
+ <div>Bearing: </div>
+ <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(0)} type="number" onChange={this.onBearingChange} />
+ </div>
+ <div className="mapbox-pitch-selection">
+ <div>Pitch: </div>
+ <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(0)} type="number" onChange={this.onPitchChange} />
+ </div>
+ <div className="mapbox-pitch-selection">
+ <div>Zoom: </div>
+ <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(0)} type="number" onChange={this.onZoomChange} />
+ </div>
+ <div className="mapbox-terrain-selection">
+ <div>Show terrain: </div>
+ <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} />
+ </div>
+ </div>
+ )}
+ {this.routeToAnimate && (
+ <div className="animation-panel" style={{ width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)` }}>
+ <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div>
+ <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
+ </div>
+ )}
+ {this.featuresFromGeocodeResults.length > 0 && (
+ <div className="mapbox-geocoding-search-results">
+ <React.Fragment>
+ <h4>Choose a location for your pin: </h4>
+ {this.featuresFromGeocodeResults
+ .filter(feature => feature.place_name)
+ .map((feature, idx) => (
+ <div
+ key={idx}
+ className="search-result-container"
+ onClick={() => {
+ this.handleSearchChange('');
+ this.addMarkerForFeature(feature);
+ }}>
+ <div className="search-result-place-name">{feature.place_name}</div>
+ </div>
+ ))}
+ </React.Fragment>
+ </div>
+ )}
+ <MapProvider>
+ <MapboxMap
+ ref={this._mapRef}
+ mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
+ viewState={(this.isAnimating || this.routeToAnimate) ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+ mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ zIndex: '0',
+ width: NumCast(this.layoutDoc._width) * parscale,
+ height: NumCast(this.layoutDoc._height) * parscale,
+ }}
+ initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+ onZoom={this.onMapZoom}
+ onMove={this.onMapMove}
+ onClick={this.handleMapClick}
+ onDblClick={this.handleMapDblClick}
+ terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+ <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
+ <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} />
+ <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
+ <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
+ {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />}
+ {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && (
+ <>
+ {!this.isStreetViewAnimation && (
+ <>
+ <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
+ <Layer
+ id="dynamic-animation-line"
+ type="line"
+ source="animated-route"
+ paint={{
+ 'line-color': this.animationLineColor,
+ 'line-width': 5,
+ }}
+ />
+ </>
+ )}
+ <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} />
+ <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} />
+ <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} />
+ <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} />
+ <Layer
+ id="start-fill-pin-base"
+ type="fill-extrusion"
+ source="start-pin-base"
+ paint={{
+ 'fill-extrusion-color': '#0bfc03',
+ 'fill-extrusion-height': 1000,
+ }}
+ />
+ <Layer
+ id="start-fill-pin-top"
+ type="fill-extrusion"
+ source="start-pin-top"
+ paint={{
+ 'fill-extrusion-color': '#0bfc03',
+ 'fill-extrusion-base': 1000,
+ 'fill-extrusion-height': 1200,
+ }}
+ />
+ <Layer
+ id="end-fill-pin-base"
+ type="fill-extrusion"
+ source="end-pin-base"
+ paint={{
+ 'fill-extrusion-color': '#eb1c1c',
+ 'fill-extrusion-height': 1000,
+ }}
+ />
+ <Layer
+ id="end-fill-pin-top"
+ type="fill-extrusion"
+ source="end-pin-top"
+ paint={{
+ 'fill-extrusion-color': '#eb1c1c',
+ 'fill-extrusion-base': 1000,
+ 'fill-extrusion-height': 1200,
+ }}
+ />
+ </>
+ )}
- <BingMapsReact
- onMapReady={this.bingMapReady} //
- bingMapsKey={bingApiKey}
- height="100%"
- mapOptions={this.bingMapOptions}
- width="100%"
- viewOptions={this.bingViewOptions}
- />
- <div>
- {!this._mapReady
- ? null
- : this.allAnnotations
- .filter(anno => !anno.layout_unrendered)
- .map((pushpin, i) => (
- <DocumentView
- key={i}
- {...this.props}
- renderDepth={this.props.renderDepth + 1}
- Document={pushpin}
- DataDoc={undefined}
- PanelWidth={returnOne}
- PanelHeight={returnOne}
- NativeWidth={returnOne}
- NativeHeight={returnOne}
- onKey={undefined}
- onDoubleClick={undefined}
- onBrowseClick={undefined}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- isDocumentActive={returnFalse}
- isContentActive={returnFalse}
- addDocTab={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- fitContentsToBox={undefined}
- focus={returnOne}
- />
- ))}
- </div>
- {/* <MapBoxInfoWindow
- key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
- place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
- markerMap={this.markerMap}
- PanelWidth={this.infoWidth}
- PanelHeight={this.infoHeight}
- moveDocument={this.moveDocument}
- isAnyChildContentActive={this.isAnyChildContentActive}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- /> */}
+ <>
+ {!this.isAnimating &&
+ this.animationPhase == 0 &&
+ this.allPushpins
+ // .filter(anno => !anno.layout_unrendered)
+ .map((pushpin, idx) => (
+ <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
+ {this.getMarkerIcon(pushpin)}
+ </Marker>
+ ))}
+ </>
+
+ {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
+ <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
+ ))} */}
+ </MapboxMap>
+ </MapProvider>
</div>
- {/* </LoadScript > */}
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
fieldKey={this.fieldKey}
- rootDoc={this.rootDoc}
+ Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth={true}
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 6bad7d724..fdd8285d5 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -1,597 +1,597 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
-import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } 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 { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { Docs } from '../../../documents/Documents';
-import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { UndoManager } from '../../../util/UndoManager';
-import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
-import { Colors } from '../../global/globalEnums';
-import { AnchorMenu } from '../../pdf/AnchorMenu';
-import { Annotation } from '../../pdf/Annotation';
-import { SidebarAnnos } from '../../SidebarAnnos';
-import { FieldView, FieldViewProps } from '../FieldView';
-import { PinProps } from '../trails';
-import './MapBox2.scss';
-import { MapBoxInfoWindow } from './MapBoxInfoWindow';
-
-/**
- * MapBox2 architecture:
- * Main component: MapBox2.tsx
- * Supporting Components: SidebarAnnos, CollectionStackingView
- *
- * MapBox2 is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
- * The main body of MapBox2 uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
- * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
- * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
- * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
- * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
- */
-
-// const _global = (window /* browser */ || global /* node */) as any;
-
-const mapContainerStyle = {
- height: '100%',
-};
-
-const defaultCenter = {
- lat: 42.360081,
- lng: -71.058884,
-};
-
-const mapOptions = {
- fullscreenControl: false,
-};
-
-const apiKey = process.env.GOOGLE_MAPS;
-
-const script = document.createElement('script');
-script.defer = true;
-script.async = true;
-script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`;
-console.log(script.src);
-document.head.appendChild(script);
-
-/**
- * Consider integrating later: allows for drawing, circling, making shapes on map
- */
-// const drawingManager = new window.google.maps.drawing.DrawingManager({
-// drawingControl: true,
-// drawingControlOptions: {
-// position: google.maps.ControlPosition.TOP_RIGHT,
-// drawingModes: [
-// google.maps.drawing.OverlayType.MARKER,
-// // currently we are not supporting the following drawing mode on map, a thought for future development
-// google.maps.drawing.OverlayType.CIRCLE,
-// google.maps.drawing.OverlayType.POLYLINE,
-// ],
-// },
-// });
-
-// options for searchbox in Google Maps Places Autocomplete API
-const options = {
- fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields
- strictBounds: false,
- types: ['establishment'], // type pf places, subject of change according to user need
-} as google.maps.places.AutocompleteOptions;
-
-@observer
-export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _disposers: { [name: string]: IReactionDisposer } = {};
- private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- @observable private _overlayAnnoInfo: Opt<Doc>;
- showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(MapBox2, fieldKey);
- }
- public get SidebarKey() {
- return this.fieldKey + '_sidebar';
- }
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
- @computed get inlineTextAnnotations() {
- return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
- }
-
- @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
- @observable private selectedPlace: Doc | undefined;
- @observable private markerMap: { [id: string]: google.maps.Marker } = {};
- @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
- @observable private inputRef = React.createRef<HTMLInputElement>();
- @observable private searchMarkers: google.maps.Marker[] = [];
- @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
- @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @computed get allSidebarDocs() {
- return DocListCast(this.dataDoc[this.SidebarKey]);
- }
- @computed get allMapMarkers() {
- return DocListCast(this.dataDoc[this.annotationKey]);
- }
- @observable private toggleAddMarker = false;
-
- @observable _showSidebar = false;
- @computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
- }
-
- static _canAnnotate = true;
- static _hadSelection: boolean = false;
- private _sidebarRef = React.createRef<SidebarAnnos>();
- private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-
- componentDidMount() {
- this.props.setContentView?.(this);
- }
-
- @action
- private setSearchBox = (searchBox: any) => {
- this.searchBox = searchBox;
- };
-
- // iterate allMarkers to size, center, and zoom map to contain all markers
- private fitBounds = (map: google.maps.Map) => {
- const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
- const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
- !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
- };
-
- /**
- * Custom control for add marker button
- * @param controlDiv
- * @param map
- */
- private CenterControl = () => {
- const controlDiv = document.createElement('div');
- controlDiv.className = 'MapBox2-addMarker';
- // Set CSS for the control border.
- const controlUI = document.createElement('div');
- controlUI.style.backgroundColor = '#fff';
- controlUI.style.borderRadius = '3px';
- controlUI.style.cursor = 'pointer';
- controlUI.style.marginTop = '10px';
- controlUI.style.borderRadius = '4px';
- controlUI.style.marginBottom = '22px';
- controlUI.style.textAlign = 'center';
- controlUI.style.position = 'absolute';
- controlUI.style.width = '32px';
- controlUI.style.height = '32px';
- controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.';
-
- const plIcon = document.createElement('img');
- plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png';
- plIcon.style.color = 'rgb(25,25,25)';
- plIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
- plIcon.style.fontSize = '16px';
- plIcon.style.lineHeight = '32px';
- plIcon.style.left = '18';
- plIcon.style.top = '15';
- plIcon.style.position = 'absolute';
- plIcon.width = 14;
- plIcon.height = 14;
- plIcon.innerHTML = 'Add';
- controlUI.appendChild(plIcon);
-
- // Set CSS for the control interior.
- const markerIcon = document.createElement('img');
- markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png';
- markerIcon.style.color = 'rgb(25,25,25)';
- markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
- markerIcon.style.fontSize = '16px';
- markerIcon.style.lineHeight = '32px';
- markerIcon.style.left = '-2';
- markerIcon.style.top = '1';
- markerIcon.width = 30;
- markerIcon.height = 30;
- markerIcon.style.position = 'absolute';
- markerIcon.innerHTML = 'Add';
- controlUI.appendChild(markerIcon);
-
- // Setup the click event listeners
- controlUI.addEventListener('click', () => {
- if (this.toggleAddMarker === true) {
- this.toggleAddMarker = false;
- console.log('add marker button status:' + this.toggleAddMarker);
- controlUI.style.backgroundColor = '#fff';
- markerIcon.style.color = 'rgb(25,25,25)';
- } else {
- this.toggleAddMarker = true;
- console.log('add marker button status:' + this.toggleAddMarker);
- controlUI.style.backgroundColor = '#4476f7';
- markerIcon.style.color = 'rgb(255,255,255)';
- }
- });
- controlDiv.appendChild(controlUI);
- return controlDiv;
- };
-
- /**
- * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list
- * @param position - the LatLng position where the marker is placed
- * @param map
- */
- @action
- private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => {
- const marker = new google.maps.Marker({
- position: position,
- map: map,
- });
- map.panTo(position);
- const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
- this.addDocument(mapMarker, this.annotationKey);
- };
-
- _loadPending = true;
- /**
- * store a reference to google map instance
- * setup the drawing manager on the top right corner of map
- * fit map bounds to contain all markers
- * @param map
- */
- @action
- private loadHandler = (map: google.maps.Map) => {
- this._map = map;
- this._loadPending = true;
- const centerControlDiv = this.CenterControl();
- map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv);
- //drawingManager.setMap(map);
- // if (navigator.geolocation) {
- // navigator.geolocation.getCurrentPosition(
- // (position: Position) => {
- // const pos = {
- // lat: position.coords.latitude,
- // lng: position.coords.longitude,
- // };
- // this._map.setCenter(pos);
- // }
- // );
- // } else {
- // alert("Your geolocation is not supported by browser.")
- // };
- map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
- map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
- setTimeout(() => {
- if (this._loadPending && this._map.getBounds()) {
- this._loadPending = false;
- this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- }, 250);
- // listener to addmarker event
- this._map.addListener('click', (e: MouseEvent) => {
- if (this.toggleAddMarker === true) {
- this.placeMarker((e as any).latLng, map);
- }
- });
- };
-
- @action
- centered = () => {
- if (this._loadPending && this._map.getBounds()) {
- this._loadPending = false;
- this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- this.dataDoc.mapLat = this._map.getCenter()?.lat();
- this.dataDoc.mapLng = this._map.getCenter()?.lng();
- };
-
- @action
- zoomChanged = () => {
- if (this._loadPending && this._map.getBounds()) {
- this._loadPending = false;
- this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- this.dataDoc.map_zoom = this._map.getZoom();
- };
-
- /**
- * Load and render all map markers
- * @param marker
- * @param place
- */
- @action
- private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => {
- place[Id] ? (this.markerMap[place[Id]] = marker) : null;
- };
-
- /**
- * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true
- * @param e
- * @param place
- */
- @action
- private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => {
- // set which place was clicked
- this.selectedPlace = place;
- place.infoWindowOpen = true;
- };
-
- /**
- * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
- * @param doc
- * @param sidebarKey
- * @returns
- */
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
- console.log('print all sidebar Docs');
- if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
- docs.forEach(doc => {
- if (doc.lat !== undefined && doc.lng !== undefined) {
- const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng);
- if (existingMarker) {
- Doc.AddDocToList(existingMarker, 'data', doc);
- } else {
- const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
- this.addDocument(marker, this.annotationKey);
- }
- }
- }); //add to annotation list
-
- return this.addDocument(doc, sidebarKey); // add to sidebar list
- };
-
- /**
- * Removing documents from the sidebar
- * @param doc
- * @param sidebarKey
- * @returns
- */
- sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
- if (this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
- return this.removeDocument(doc, sidebarKey);
- };
-
- /**
- * Toggle sidebar onclick the tiny comment button on the top right corner
- * @param e
- */
- sidebarBtnDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(
- this,
- e,
- (e, down, delta) =>
- runInAction(() => {
- const localDelta = this.props
- .ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
- .transformDirection(delta[0], delta[1]);
- const fullWidth = NumCast(this.layoutDoc._width);
- const mapWidth = fullWidth - this.sidebarWidth();
- if (this.sidebarWidth() + localDelta[0] > 0) {
- this._showSidebar = true;
- this.layoutDoc._width = fullWidth + localDelta[0];
- this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
- } else {
- this._showSidebar = false;
- this.layoutDoc._width = mapWidth;
- this.layoutDoc._layout_sidebarWidthPercent = '0%';
- }
- return false;
- }),
- emptyFunction,
- () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
- );
- };
-
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
- @computed get layout_sidebarWidthPercent() {
- return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
- }
- @computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
- }
-
- /**
- * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted;
- * add a customized temporary marker on the map
- */
- @action
- private handlePlaceChanged = () => {
- const place = this.searchBox.getPlace();
-
- if (!place.geometry || !place.geometry.location) {
- // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed
- window.alert("No details available for input: '" + place.name + "'");
- return;
- }
-
- // zoom in on the location of the search result
- if (place.geometry.viewport) {
- this._map.fitBounds(place.geometry.viewport);
- } else {
- this._map.setCenter(place.geometry.location);
- this._map.setZoom(17);
- }
-
- // customize icon => customized icon for the nature of the location selected
- const icon = {
- url: place.icon as string,
- size: new google.maps.Size(71, 71),
- origin: new google.maps.Point(0, 0),
- anchor: new google.maps.Point(17, 34),
- scaledSize: new google.maps.Size(25, 25),
- };
-
- // put temporary cutomized marker on searched location
- this.searchMarkers.forEach(marker => {
- marker.setMap(null);
- });
- this.searchMarkers = [];
- this.searchMarkers.push(
- new window.google.maps.Marker({
- map: this._map,
- icon,
- title: place.name,
- position: place.geometry.location,
- })
- );
- };
-
- /**
- * Handles toggle of sidebar on click the little comment button
- */
- @computed get sidebarHandle() {
- return (
- <div
- className="MapBox2-overlayButton-sidebar"
- key="sidebar"
- title="Toggle Sidebar"
- style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
- top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
- backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
- }}
- onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
- </div>
- );
- }
-
- // TODO: Adding highlight box layer to Maps
- @action
- toggleSidebar = () => {
- //1.2 * w * ? = .2 * w .2/1.2
- const prevWidth = this.sidebarWidth();
- this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
- this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
- };
-
- sidebarDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
- };
- sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
- 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%';
- e.preventDefault();
- return false;
- };
-
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
-
- addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- return this.addDocument(doc, annotationKey);
- };
-
- pointerEvents = () => {
- return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
- };
- @computed get annotationLayer() {
- return (
- <div className="MapBox2-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations
- .sort((a, b) => NumCast(a.y) - NumCast(b.y))
- .map(anno => (
- <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
- ))}
- </div>
- );
- }
-
- getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc;
-
- /**
- * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
- * @returns
- */
- private renderMarkers = () => {
- return this.allMapMarkers.map(place => (
- <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} />
- ));
- };
-
- // TODO: auto center on select a document in the sidebar
- private handleMapCenter = (map: google.maps.Map) => {
- // console.log("print the selected views in selectionManager:")
- // if (SelectionManager.Views().lastElement()) {
- // console.log(SelectionManager.Views().lastElement());
- // }
- };
-
- panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
- panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
- infoWidth = () => this.props.PanelWidth() / 5;
- infoHeight = () => this.props.PanelHeight() / 5;
- anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
- savedAnnotations = () => this._savedAnnotations;
-
- get MicrosoftMaps() {
- return (window as any).Microsoft.Maps;
- }
- render() {
- const renderAnnotations = (childFilters?: () => string[]) => null;
- return (
- <div className="MapBox2" ref={this._ref}>
- <div
- className="MapBox2-wrapper"
- onWheel={e => e.stopPropagation()}
- onPointerDown={async e => {
- e.button === 0 && !e.ctrlKey && e.stopPropagation();
- }}
- style={{ width: `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
- <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
- {this.annotationLayer}
-
- <div>
- <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
- <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
- <input className="MapBox2-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
- </Autocomplete>
-
- {this.renderMarkers()}
- {this.allMapMarkers
- .filter(marker => marker.infoWindowOpen)
- .map(marker => (
- <MapBoxInfoWindow
- key={marker[Id]}
- {...this.props}
- setContentView={emptyFunction}
- place={marker}
- markerMap={this.markerMap}
- PanelWidth={this.infoWidth}
- PanelHeight={this.infoHeight}
- moveDocument={this.moveDocument}
- isAnyChildContentActive={this.isAnyChildContentActive}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- />
- ))}
- {/* {this.handleMapCenter(this._map)} */}
- </GoogleMap>
- </div>
- </div>
- {/* </LoadScript > */}
- <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- <SidebarAnnos
- ref={this._sidebarRef}
- {...this.props}
- fieldKey={this.fieldKey}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- usePanelWidth={true}
- showSidebar={this.SidebarShown}
- nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- PanelWidth={this.sidebarWidth}
- sidebarAddDocument={this.sidebarAddDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.sidebarRemoveDocument}
- />
- </div>
- {this.sidebarHandle}
- </div>
- );
- }
-}
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
+// import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } 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 { NumCast, StrCast } from '../../../../fields/Types';
+// import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+// import { Docs } from '../../../documents/Documents';
+// import { DragManager } from '../../../util/DragManager';
+// import { SnappingManager } from '../../../util/SnappingManager';
+// import { UndoManager } from '../../../util/UndoManager';
+// import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+// import { Colors } from '../../global/globalEnums';
+// import { AnchorMenu } from '../../pdf/AnchorMenu';
+// import { Annotation } from '../../pdf/Annotation';
+// import { SidebarAnnos } from '../../SidebarAnnos';
+// import { FieldView, FieldViewProps } from '../FieldView';
+// import { PinProps } from '../trails';
+// import './MapBox2.scss';
+// import { MapBoxInfoWindow } from './MapBoxInfoWindow';
+
+// /**
+// * MapBox2 architecture:
+// * Main component: MapBox2.tsx
+// * Supporting Components: SidebarAnnos, CollectionStackingView
+// *
+// * MapBox2 is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
+// * The main body of MapBox2 uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
+// * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
+// * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
+// * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
+// * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
+// */
+
+// // const _global = (window /* browser */ || global /* node */) as any;
+
+// const mapContainerStyle = {
+// height: '100%',
+// };
+
+// const defaultCenter = {
+// lat: 42.360081,
+// lng: -71.058884,
+// };
+
+// const mapOptions = {
+// fullscreenControl: false,
+// };
+
+// const apiKey = process.env.GOOGLE_MAPS;
+
+// const script = document.createElement('script');
+// script.defer = true;
+// script.async = true;
+// script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`;
+// console.log(script.src);
+// document.head.appendChild(script);
+
+// /**
+// * Consider integrating later: allows for drawing, circling, making shapes on map
+// */
+// // const drawingManager = new window.google.maps.drawing.DrawingManager({
+// // drawingControl: true,
+// // drawingControlOptions: {
+// // position: google.maps.ControlPosition.TOP_RIGHT,
+// // drawingModes: [
+// // google.maps.drawing.OverlayType.MARKER,
+// // // currently we are not supporting the following drawing mode on map, a thought for future development
+// // google.maps.drawing.OverlayType.CIRCLE,
+// // google.maps.drawing.OverlayType.POLYLINE,
+// // ],
+// // },
+// // });
+
+// // options for searchbox in Google Maps Places Autocomplete API
+// const options = {
+// fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields
+// strictBounds: false,
+// types: ['establishment'], // type pf places, subject of change according to user need
+// } as google.maps.places.AutocompleteOptions;
+
+// @observer
+// export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
+// private _dropDisposer?: DragManager.DragDropDisposer;
+// private _disposers: { [name: string]: IReactionDisposer } = {};
+// private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+// @observable private _overlayAnnoInfo: Opt<Doc>;
+// showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+// public static LayoutString(fieldKey: string) {
+// return FieldView.LayoutString(MapBox2, fieldKey);
+// }
+// public get SidebarKey() {
+// return this.fieldKey + '_sidebar';
+// }
+// private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+// @computed get inlineTextAnnotations() {
+// return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
+// }
+
+// @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
+// @observable private selectedPlace: Doc | undefined = undefined;
+// @observable private markerMap: { [id: string]: google.maps.Marker } = {};
+// @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
+// @observable private inputRef = React.createRef<HTMLInputElement>();
+// @observable private searchMarkers: google.maps.Marker[] = [];
+// @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
+// @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+// @computed get allSidebarDocs() {
+// return DocListCast(this.dataDoc[this.SidebarKey]);
+// }
+// @computed get allMapMarkers() {
+// return DocListCast(this.dataDoc[this.annotationKey]);
+// }
+// @observable private toggleAddMarker = false;
+
+// @observable _showSidebar = false;
+// @computed get SidebarShown() {
+// return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+// }
+
+// static _canAnnotate = true;
+// static _hadSelection: boolean = false;
+// private _sidebarRef = React.createRef<SidebarAnnos>();
+// private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+
+// componentDidMount() {
+// this.props.setContentView?.(this);
+// }
+
+// @action
+// private setSearchBox = (searchBox: any) => {
+// this.searchBox = searchBox;
+// };
+
+// // iterate allMarkers to size, center, and zoom map to contain all markers
+// private fitBounds = (map: google.maps.Map) => {
+// const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
+// const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
+// !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
+// };
+
+// /**
+// * Custom control for add marker button
+// * @param controlDiv
+// * @param map
+// */
+// private CenterControl = () => {
+// const controlDiv = document.createElement('div');
+// controlDiv.className = 'MapBox2-addMarker';
+// // Set CSS for the control border.
+// const controlUI = document.createElement('div');
+// controlUI.style.backgroundColor = '#fff';
+// controlUI.style.borderRadius = '3px';
+// controlUI.style.cursor = 'pointer';
+// controlUI.style.marginTop = '10px';
+// controlUI.style.borderRadius = '4px';
+// controlUI.style.marginBottom = '22px';
+// controlUI.style.textAlign = 'center';
+// controlUI.style.position = 'absolute';
+// controlUI.style.width = '32px';
+// controlUI.style.height = '32px';
+// controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.';
+
+// const plIcon = document.createElement('img');
+// plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png';
+// plIcon.style.color = 'rgb(25,25,25)';
+// plIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
+// plIcon.style.fontSize = '16px';
+// plIcon.style.lineHeight = '32px';
+// plIcon.style.left = '18';
+// plIcon.style.top = '15';
+// plIcon.style.position = 'absolute';
+// plIcon.width = 14;
+// plIcon.height = 14;
+// plIcon.innerHTML = 'Add';
+// controlUI.appendChild(plIcon);
+
+// // Set CSS for the control interior.
+// const markerIcon = document.createElement('img');
+// markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png';
+// markerIcon.style.color = 'rgb(25,25,25)';
+// markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
+// markerIcon.style.fontSize = '16px';
+// markerIcon.style.lineHeight = '32px';
+// markerIcon.style.left = '-2';
+// markerIcon.style.top = '1';
+// markerIcon.width = 30;
+// markerIcon.height = 30;
+// markerIcon.style.position = 'absolute';
+// markerIcon.innerHTML = 'Add';
+// controlUI.appendChild(markerIcon);
+
+// // Setup the click event listeners
+// controlUI.addEventListener('click', () => {
+// if (this.toggleAddMarker === true) {
+// this.toggleAddMarker = false;
+// console.log('add marker button status:' + this.toggleAddMarker);
+// controlUI.style.backgroundColor = '#fff';
+// markerIcon.style.color = 'rgb(25,25,25)';
+// } else {
+// this.toggleAddMarker = true;
+// console.log('add marker button status:' + this.toggleAddMarker);
+// controlUI.style.backgroundColor = '#4476f7';
+// markerIcon.style.color = 'rgb(255,255,255)';
+// }
+// });
+// controlDiv.appendChild(controlUI);
+// return controlDiv;
+// };
+
+// /**
+// * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list
+// * @param position - the LatLng position where the marker is placed
+// * @param map
+// */
+// @action
+// private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => {
+// const marker = new google.maps.Marker({
+// position: position,
+// map: map,
+// });
+// map.panTo(position);
+// const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
+// this.addDocument(mapMarker, this.annotationKey);
+// };
+
+// _loadPending = true;
+// /**
+// * store a reference to google map instance
+// * setup the drawing manager on the top right corner of map
+// * fit map bounds to contain all markers
+// * @param map
+// */
+// @action
+// private loadHandler = (map: google.maps.Map) => {
+// this._map = map;
+// this._loadPending = true;
+// const centerControlDiv = this.CenterControl();
+// map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv);
+// //drawingManager.setMap(map);
+// // if (navigator.geolocation) {
+// // navigator.geolocation.getCurrentPosition(
+// // (position: Position) => {
+// // const pos = {
+// // lat: position.coords.latitude,
+// // lng: position.coords.longitude,
+// // };
+// // this._map.setCenter(pos);
+// // }
+// // );
+// // } else {
+// // alert("Your geolocation is not supported by browser.")
+// // };
+// map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
+// map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
+// setTimeout(() => {
+// if (this._loadPending && this._map.getBounds()) {
+// this._loadPending = false;
+// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
+// }
+// }, 250);
+// // listener to addmarker event
+// this._map.addListener('click', (e: MouseEvent) => {
+// if (this.toggleAddMarker === true) {
+// this.placeMarker((e as any).latLng, map);
+// }
+// });
+// };
+
+// @action
+// centered = () => {
+// if (this._loadPending && this._map.getBounds()) {
+// this._loadPending = false;
+// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
+// }
+// this.dataDoc.mapLat = this._map.getCenter()?.lat();
+// this.dataDoc.mapLng = this._map.getCenter()?.lng();
+// };
+
+// @action
+// zoomChanged = () => {
+// if (this._loadPending && this._map.getBounds()) {
+// this._loadPending = false;
+// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
+// }
+// this.dataDoc.map_zoom = this._map.getZoom();
+// };
+
+// /**
+// * Load and render all map markers
+// * @param marker
+// * @param place
+// */
+// @action
+// private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => {
+// place[Id] ? (this.markerMap[place[Id]] = marker) : null;
+// };
+
+// /**
+// * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true
+// * @param e
+// * @param place
+// */
+// @action
+// private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => {
+// // set which place was clicked
+// this.selectedPlace = place;
+// place.infoWindowOpen = true;
+// };
+
+// /**
+// * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
+// * @param doc
+// * @param sidebarKey
+// * @returns
+// */
+// sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+// console.log('print all sidebar Docs');
+// if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
+// const docs = doc instanceof Doc ? [doc] : doc;
+// docs.forEach(doc => {
+// if (doc.lat !== undefined && doc.lng !== undefined) {
+// const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng);
+// if (existingMarker) {
+// Doc.AddDocToList(existingMarker, 'data', doc);
+// } else {
+// const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
+// this.addDocument(marker, this.annotationKey);
+// }
+// }
+// }); //add to annotation list
+
+// return this.addDocument(doc, sidebarKey); // add to sidebar list
+// };
+
+// /**
+// * Removing documents from the sidebar
+// * @param doc
+// * @param sidebarKey
+// * @returns
+// */
+// sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+// if (this.layoutDoc._layout_showSidebar) this.toggleSidebar();
+// const docs = doc instanceof Doc ? [doc] : doc;
+// return this.removeDocument(doc, sidebarKey);
+// };
+
+// /**
+// * Toggle sidebar onclick the tiny comment button on the top right corner
+// * @param e
+// */
+// sidebarBtnDown = (e: React.PointerEvent) => {
+// setupMoveUpEvents(
+// this,
+// e,
+// (e, down, delta) =>
+// runInAction(() => {
+// const localDelta = this.props
+// .ScreenToLocalTransform()
+// .scale(this.props.NativeDimScaling?.() || 1)
+// .transformDirection(delta[0], delta[1]);
+// const fullWidth = NumCast(this.layoutDoc._width);
+// const mapWidth = fullWidth - this.sidebarWidth();
+// if (this.sidebarWidth() + localDelta[0] > 0) {
+// this._showSidebar = true;
+// this.layoutDoc._width = fullWidth + localDelta[0];
+// this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+// } else {
+// this._showSidebar = false;
+// this.layoutDoc._width = mapWidth;
+// this.layoutDoc._layout_sidebarWidthPercent = '0%';
+// }
+// return false;
+// }),
+// emptyFunction,
+// () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
+// );
+// };
+
+// sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+// @computed get layout_sidebarWidthPercent() {
+// return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
+// }
+// @computed get sidebarColor() {
+// return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+// }
+
+// /**
+// * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted;
+// * add a customized temporary marker on the map
+// */
+// @action
+// private handlePlaceChanged = () => {
+// const place = this.searchBox.getPlace();
+
+// if (!place.geometry || !place.geometry.location) {
+// // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed
+// window.alert("No details available for input: '" + place.name + "'");
+// return;
+// }
+
+// // zoom in on the location of the search result
+// if (place.geometry.viewport) {
+// this._map.fitBounds(place.geometry.viewport);
+// } else {
+// this._map.setCenter(place.geometry.location);
+// this._map.setZoom(17);
+// }
+
+// // customize icon => customized icon for the nature of the location selected
+// const icon = {
+// url: place.icon as string,
+// size: new google.maps.Size(71, 71),
+// origin: new google.maps.Point(0, 0),
+// anchor: new google.maps.Point(17, 34),
+// scaledSize: new google.maps.Size(25, 25),
+// };
+
+// // put temporary cutomized marker on searched location
+// this.searchMarkers.forEach(marker => {
+// marker.setMap(null);
+// });
+// this.searchMarkers = [];
+// this.searchMarkers.push(
+// new window.google.maps.Marker({
+// map: this._map,
+// icon,
+// title: place.name,
+// position: place.geometry.location,
+// })
+// );
+// };
+
+// /**
+// * Handles toggle of sidebar on click the little comment button
+// */
+// @computed get sidebarHandle() {
+// return (
+// <div
+// className="MapBox2-overlayButton-sidebar"
+// key="sidebar"
+// title="Toggle Sidebar"
+// style={{
+// display: !this.props.isContentActive() ? 'none' : undefined,
+// top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
+// backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+// }}
+// onPointerDown={this.sidebarBtnDown}>
+// <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+// </div>
+// );
+// }
+
+// // TODO: Adding highlight box layer to Maps
+// @action
+// toggleSidebar = () => {
+// //1.2 * w * ? = .2 * w .2/1.2
+// const prevWidth = this.sidebarWidth();
+// this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+// this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+// };
+
+// sidebarDown = (e: React.PointerEvent) => {
+// setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
+// };
+// sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+// 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%';
+// e.preventDefault();
+// return false;
+// };
+
+// setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+
+// addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+// return this.addDocument(doc, annotationKey);
+// };
+
+// pointerEvents = () => {
+// return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.IsDragging ? undefined : 'none';
+// };
+// @computed get annotationLayer() {
+// return (
+// <div className="MapBox2-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
+// {this.inlineTextAnnotations
+// .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+// .map(anno => (
+// <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
+// ))}
+// </div>
+// );
+// }
+
+// getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.Document;
+
+// /**
+// * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
+// * @returns
+// */
+// private renderMarkers = () => {
+// return this.allMapMarkers.map(place => (
+// <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} />
+// ));
+// };
+
+// // TODO: auto center on select a document in the sidebar
+// private handleMapCenter = (map: google.maps.Map) => {
+// // console.log("print the selected views in selectionManager:")
+// // if (SelectionManager.Views.lastElement()) {
+// // console.log(SelectionManager.Views.lastElement());
+// // }
+// };
+
+// panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+// panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+// scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+// transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+// opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
+// infoWidth = () => this.props.PanelWidth() / 5;
+// infoHeight = () => this.props.PanelHeight() / 5;
+// anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+// savedAnnotations = () => this._savedAnnotations;
+
+// get MicrosoftMaps() {
+// return (window as any).Microsoft.Maps;
+// }
+// render() {
+// const renderAnnotations = (childFilters?: () => string[]) => null;
+// return (
+// <div className="MapBox2" ref={this._ref}>
+// <div
+// className="MapBox2-wrapper"
+// onWheel={e => e.stopPropagation()}
+// onPointerDown={async e => {
+// e.button === 0 && !e.ctrlKey && e.stopPropagation();
+// }}
+// style={{ width: `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+// <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
+// {renderAnnotations(this.opaqueFilter)}
+// {SnappingManager.IsDragging ? null : renderAnnotations()}
+// {this.annotationLayer}
+
+// <div>
+// <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
+// <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
+// <input className="MapBox2-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
+// </Autocomplete>
+
+// {this.renderMarkers()}
+// {this.allMapMarkers
+// .filter(marker => marker.infoWindowOpen)
+// .map(marker => (
+// <MapBoxInfoWindow
+// key={marker[Id]}
+// {...this.props}
+// setContentView={emptyFunction}
+// place={marker}
+// markerMap={this.markerMap}
+// PanelWidth={this.infoWidth}
+// PanelHeight={this.infoHeight}
+// moveDocument={this.moveDocument}
+// isAnyChildContentActive={this.isAnyChildContentActive}
+// whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+// />
+// ))}
+// {/* {this.handleMapCenter(this._map)} */}
+// </GoogleMap>
+// </div>
+// </div>
+// {/* </LoadScript > */}
+// <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+// <SidebarAnnos
+// ref={this._sidebarRef}
+// {...this.props}
+// fieldKey={this.fieldKey}
+// Document={this.Document}
+// layoutDoc={this.layoutDoc}
+// dataDoc={this.dataDoc}
+// usePanelWidth={true}
+// showSidebar={this.SidebarShown}
+// nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+// whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+// PanelWidth={this.sidebarWidth}
+// sidebarAddDocument={this.sidebarAddDocument}
+// moveDocument={this.moveDocument}
+// removeDocument={this.sidebarRemoveDocument}
+// />
+// </div>
+// {this.sidebarHandle}
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 66c47d131..a9c6ba22c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -1,96 +1,95 @@
-import { InfoWindow } from '@react-google-maps/api';
-import { action } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
-import { Docs } from '../../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
-import { CollectionStackingView } from '../../collections/CollectionStackingView';
-import { ViewBoxAnnotatableProps } from '../../DocComponent';
-import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import './MapBox.scss';
+// import { InfoWindow } from '@react-google-maps/api';
+// import { action } from 'mobx';
+// import { observer } from 'mobx-react';
+// import * as React from 'react';
+// import { Doc } from '../../../../fields/Doc';
+// import { Id } from '../../../../fields/FieldSymbols';
+// import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+// import { Docs } from '../../../documents/Documents';
+// import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+// import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
+// import { CollectionStackingView } from '../../collections/CollectionStackingView';
+// import { ViewBoxAnnotatableProps } from '../../DocComponent';
+// import { FieldViewProps } from '../FieldView';
+// import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+// import './MapBox.scss';
-interface MapBoxInfoWindowProps {
- place: Doc;
- renderDepth: number;
- markerMap: { [id: string]: google.maps.Marker };
- isAnyChildContentActive: () => boolean;
-}
-@observer
-export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
- @action
- private handleInfoWindowClose = () => {
- if (this.props.place.infoWindowOpen) {
- this.props.place.infoWindowOpen = false;
- }
- this.props.place.infoWindowOpen = false;
- };
+// interface MapBoxInfoWindowProps {
+// place: Doc;
+// renderDepth: number;
+// markerMap: { [id: string]: google.maps.Marker };
+// isAnyChildContentActive: () => boolean;
+// }
+// @observer
+// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
+// @action
+// private handleInfoWindowClose = () => {
+// if (this.props.place.infoWindowOpen) {
+// this.props.place.infoWindowOpen = false;
+// }
+// this.props.place.infoWindowOpen = false;
+// };
- addNoteClick = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
- const newBox = Docs.Create.TextDocument('Note', { _layout_autoHeight: true });
- FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- Doc.AddDocToList(this.props.place, 'data', newBox);
- this._stack?.scrollToBottom();
- e.stopPropagation();
- e.preventDefault();
- });
- };
+// addNoteClick = (e: React.PointerEvent) => {
+// setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
+// const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true });
+// FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+// Doc.AddDocToList(this.props.place, 'data', newDoc);
+// this._stack?.scrollToBottom();
+// e.stopPropagation();
+// e.preventDefault();
+// });
+// };
- _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined;
- childLayoutFitWidth = (doc: Doc) => doc.type === DocumentType.RTF;
- addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean);
- removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
- render() {
- return (
- <InfoWindow
- // anchor={this.props.markerMap[this.props.place[Id]]}
- onCloseClick={this.handleInfoWindowClose}>
- <div className="mapbox-infowindow">
- <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
- <CollectionStackingView
- ref={r => (this._stack = r)}
- {...this.props}
- setContentView={emptyFunction}
- Document={this.props.place}
- DataDoc={undefined}
- fieldKey="data"
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- childFilters={returnEmptyFilter}
- setHeight={emptyFunction}
- isAnnotationOverlay={false}
- select={emptyFunction}
- NativeDimScaling={returnOne}
- isContentActive={returnTrue}
- chromeHidden={true}
- rootSelected={returnFalse}
- childHideResizeHandles={returnTrue}
- childHideDecorationTitle={returnTrue}
- childLayoutFitWidth={this.childLayoutFitWidth}
- // childDocumentsActive={returnFalse}
- removeDocument={this.removeDoc}
- addDocument={this.addDoc}
- renderDepth={this.props.renderDepth + 1}
- type_collection={CollectionViewType.Stacking}
- pointerEvents={returnAll}
- />
- </div>
- <hr />
- <div
- onPointerDown={this.addNoteClick}
- onClick={e => {
- e.stopPropagation();
- e.preventDefault();
- }}>
- Add Note
- </div>
- </div>
- </InfoWindow>
- );
- }
-}
+// _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined;
+// childLayoutFitWidth = (doc: Doc) => doc.type === DocumentType.RTF;
+// addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean);
+// removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
+// render() {
+// return (
+// <InfoWindow
+// // anchor={this.props.markerMap[this.props.place[Id]]}
+// onCloseClick={this.handleInfoWindowClose}>
+// <div className="mapbox-infowindow">
+// <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
+// <CollectionStackingView
+// ref={r => (this._stack = r)}
+// {...this.props}
+// setContentView={emptyFunction}
+// Document={this.props.place}
+// TemplateDataDocument={undefined}
+// fieldKey="data"
+// NativeWidth={returnZero}
+// NativeHeight={returnZero}
+// childFilters={returnEmptyFilter}
+// setHeight={emptyFunction}
+// isAnnotationOverlay={false}
+// select={emptyFunction}
+// NativeDimScaling={returnOne}
+// isContentActive={returnTrue}
+// chromeHidden={true}
+// childHideResizeHandles={true}
+// childHideDecorationTitle={true}
+// childLayoutFitWidth={this.childLayoutFitWidth}
+// // childDocumentsActive={returnFalse}
+// removeDocument={this.removeDoc}
+// addDocument={this.addDoc}
+// renderDepth={this.props.renderDepth + 1}
+// type_collection={CollectionViewType.Stacking}
+// pointerEvents={returnAll}
+// />
+// </div>
+// <hr />
+// <div
+// onPointerDown={this.addNoteClick}
+// onClick={e => {
+// e.stopPropagation();
+// e.preventDefault();
+// }}>
+// Add Note
+// </div>
+// </div>
+// </InfoWindow>
+// );
+// }
+// }
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 42bada0ef..8760c8600 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,31 +1,27 @@
-import { observer } from 'mobx-react';
-// import { SettingsManager } from '../../../util/SettingsManager';
+import * as React from 'react';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
-import React = require('react');
-import { computed } from 'mobx';
import { MapBox } from './MapBox';
/**
* Map Pushpin doc class
*/
-@observer
export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapPushpinBox, fieldKey);
}
componentDidMount() {
- this.mapBoxView.addPushpin(this.rootDoc);
+ this.mapBoxView.addPushpin(this.Document);
}
componentWillUnmount() {
- this.mapBoxView.deletePushpin(this.rootDoc);
+ this.mapBoxView.deletePushpin(this.Document);
}
- @computed get mapBoxView() {
- return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as MapBox;
+ get mapBoxView() {
+ return this.props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
}
- @computed get mapBox() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+ get mapBox() {
+ return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
}
render() {
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
new file mode 100644
index 000000000..592330ac2
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -0,0 +1,139 @@
+
+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';
+const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
+
+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 response = await fetch(url);
+ const data = await response.json();
+ return data.features;
+ } 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 response = await fetch(url);
+ const data = await response.json();
+ return data.features;
+ } 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())
+ );
+ });
+
+ const results = await Promise.all(directionsPromises);
+
+ const routeInfoMap: Record<TransportationType, any> = {
+ '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,
+ };
+ }
+ });
+
+ return routeInfoMap;
+
+ // 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`
+ }
+ }
+
+ private static metersToMiles = (meters: number) => {
+ return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
+ }
+
+}
+
+// const drivingQuery = await fetch(
+// `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const cyclingQuery = await fetch(
+// `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const walkingQuery = await fetch(
+// `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const drivingJson = await drivingQuery.json();
+// const cyclingJson = await cyclingQuery.json();
+// const walkingJson = await walkingQuery.json();
+
+// console.log("Driving: ", drivingJson);
+// console.log("Cycling: ", cyclingJson);
+// console.log("Waling: ", walkingJson);
+
+// const routeMap = {
+// 'driving': drivingJson.routes[0],
+// 'cycling': cyclingJson.routes[0],
+// 'walking': walkingJson.routes[0]
+// }
+
+// const routeInfoMap: Record<TransportationType, any> = {
+// 'driving': {},
+// 'cycling': {},
+// 'walking': {},
+// };
+
+// Object.entries(routeMap).forEach(([key, routeData]) => {
+// const transportationTypeKey = key as TransportationType;
+// const geometry = routeData.geometry;
+// const coordinates = geometry.coordinates;
+
+// console.log(coordinates);
+
+// routeInfoMap[transportationTypeKey] = {
+// duration: this.secondsToMinutesHours(routeData.duration),
+// 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
new file mode 100644
index 000000000..146f296c1
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -0,0 +1,76 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faShopify } from '@fortawesome/free-brands-svg-icons';
+import { faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faLocationDot, faLocationPin, faMapPin, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React = require('react');
+
+export class MarkerIcons {
+
+ // static getMapboxIcon = (color: string) => {
+ // return (
+ // <svg xmlns="http://www.w3.org/2000/svg" id="marker" data-name="marker" width="20" height="48" viewBox="0 0 20 35">
+ // <g id="mapbox-marker-icon">
+ // <g id="icon">
+ // <ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" />
+ // <g id="mask" opacity="0.3">
+ // <g id="group">
+ // <path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fillRule="evenodd"/>
+ // </g>
+ // </g>
+ // <path id="color" fill={color} strokeWidth="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
+ // <path id="circle" fill="#fff" stroke='white' strokeWidth="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
+ // </g>
+ // </g>
+ // <rect width="20" height="48" fill="none"/>
+ // </svg>
+ // )
+ // }
+
+ static getFontAwesomeIcon(key: string, size: string, color?: string): JSX.Element {
+ const icon: IconProp = MarkerIcons.FAMarkerIconsMap[key];
+ const iconProps: any = { icon };
+
+ if (color) {
+ iconProps.color = color;
+ }
+
+ return (<FontAwesomeIcon {...iconProps} size={size} />);
+
+
+ }
+
+ static FAMarkerIconsMap: {[key: string]: IconProp} = {
+ 'MAP_PIN': faLocationDot,
+ 'RESTAURANT_ICON': faUtensils,
+ 'HOTEL_ICON': faHotel,
+ 'HOUSE_ICON': faHouse,
+ 'AIRPLANE_ICON': faPlane,
+ 'CAR_ICON': faCar,
+ 'BUS_ICON': faBus,
+ 'TRAIN_ICON': faTrainSubway,
+ 'BICYCLE_ICON': faBicycle,
+ 'PARKING_ICON': faSquareParking,
+ 'PHOTO_ICON': faCameraRetro,
+ 'CAFE_ICON': faMugSaucer,
+ 'STAR_ICON': faStar,
+ 'SHOPPING_CART_ICON': faCartShopping,
+ 'SHOPIFY_ICON': faShopify,
+ 'SHOP_ICON': faShop,
+ 'SHIRT_ICON': faShirt,
+ 'FOOD_ICON': faBowlFood,
+ 'LANDMARK_ICON': faLandmark,
+ 'HOSPITAL_ICON': faHospital,
+ 'NATURE_ICON': faTree,
+ 'HIKING_ICON': faPersonHiking,
+ 'SOCCER_ICON': faFutbol,
+ 'VOLLEYBALL_ICON': faVolleyball,
+ 'BASKETBALL_ICON': faBasketball,
+ 'HOCKEY_ICON': faHockeyPuck,
+ 'FOOTBALL_ICON': faFootball,
+ 'SCHOOL_ICON': faSchool,
+ 'THEATER_ICON': faMasksTheater,
+ 'FILM_ICON': faFilm
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png b/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png
new file mode 100644
index 000000000..8b686e2aa
--- /dev/null
+++ b/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png
Binary files differ
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
new file mode 100644
index 000000000..6a14427c0
--- /dev/null
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -0,0 +1,838 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import BingMapsReact from 'bingmaps-react';
+import { Button, EditableText, IconButton, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+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 { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+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 { undoable, UndoManager } from '../../../util/UndoManager';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { SidebarAnnos } from '../../SidebarAnnos';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+import { PinProps, PresBox } from '../trails';
+import './MapBox.scss';
+import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
+import { MapProvider, Map as MapboxMap } from 'react-map-gl';
+
+// amongus
+/**
+ * MapBox architecture:
+ * Main component: MapBox.tsx
+ * Supporting Components: SidebarAnnos, CollectionStackingView
+ *
+ * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
+ * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
+ * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
+ * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
+ * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
+ * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
+ */
+
+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
+ */
+// const drawingManager = new window.google.maps.drawing.DrawingManager({
+// drawingControl: true,
+// drawingControlOptions: {
+// position: google.maps.ControlPosition.TOP_RIGHT,
+// drawingModes: [
+// google.maps.drawing.OverlayType.MARKER,
+// // currently we are not supporting the following drawing mode on map, a thought for future development
+// google.maps.drawing.OverlayType.CIRCLE,
+// google.maps.drawing.OverlayType.POLYLINE,
+// ],
+// },
+// });
+
+@observer
+export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(MapBoxContainer, fieldKey);
+ }
+ private _dragRef = React.createRef<HTMLDivElement>();
+ private _sidebarRef = React.createRef<SidebarAnnos>();
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+ private _disposers: { [key: string]: IReactionDisposer } = {};
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+
+ @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @computed get allSidebarDocs() {
+ return DocListCast(this.dataDoc[this.SidebarKey]);
+ }
+ // this list contains pushpins and configs
+ @computed get allAnnotations() {
+ return DocListCast(this.dataDoc[this.annotationKey]);
+ }
+ @computed get allPushpins() {
+ return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
+ }
+ @computed get SidebarShown() {
+ return this.layoutDoc._layout_showSidebar ? true : false;
+ }
+ @computed get sidebarWidthPercent() {
+ return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ }
+ @computed get SidebarKey() {
+ return this.fieldKey + '_sidebar';
+ }
+
+ componentDidMount() {
+ this._unmounting = false;
+ this.props.setContentView?.(this);
+ }
+
+ _unmounting = false;
+ componentWillUnmount(): void {
+ this._unmounting = true;
+ this.deselectPin();
+ this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
+ Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
+ }
+
+ /**
+ * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
+ * @param doc
+ * @param sidebarKey
+ * @returns
+ */
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
+ const docs = doc instanceof Doc ? [doc] : doc;
+ 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) {
+ existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
+ }
+ if (existingPin) {
+ setTimeout(() => {
+ // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet
+ if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
+ const anchor = this.getAnchor(true, undefined, existingPin);
+ anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' });
+ doc.latitude = existingPin?.latitude;
+ doc.longitude = existingPin?.longitude;
+ }
+ });
+ }
+ }); //add to annotation list
+
+ return this.addDocument(doc, 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));
+ return this.removeDocument(doc, annotationKey, undefined);
+ };
+
+ /**
+ * Removing documents from the sidebar
+ * @param doc
+ * @param sidebarKey
+ * @returns
+ */
+ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeMapDocument(doc, sidebarKey);
+
+ /**
+ * Toggle sidebar onclick the tiny comment button on the top right corner
+ * @param e
+ */
+ sidebarBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) =>
+ runInAction(() => {
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const fullWidth = NumCast(this.layoutDoc._width);
+ const mapWidth = fullWidth - this.sidebarWidth();
+ if (this.sidebarWidth() + localDelta[0] > 0) {
+ this.layoutDoc._layout_showSidebar = true;
+ this.layoutDoc._width = fullWidth + localDelta[0];
+ this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+ } else {
+ this.layoutDoc._layout_showSidebar = false;
+ this.layoutDoc._width = mapWidth;
+ this.layoutDoc._layout_sidebarWidthPercent = '0%';
+ }
+ return false;
+ }),
+ emptyFunction,
+ () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
+ );
+ };
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+
+ /**
+ * Handles toggle of sidebar on click the little comment button
+ */
+ @computed get sidebarHandle() {
+ return (
+ <div
+ className="mapBox-overlayButton-sidebar"
+ key="sidebar"
+ title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ }}
+ onPointerDown={this.sidebarBtnDown}>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ </div>
+ );
+ }
+
+ // TODO: Adding highlight box layer to Maps
+ @action
+ toggleSidebar = () => {
+ const prevWidth = this.sidebarWidth();
+ this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+ this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ };
+
+ startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const sourceAnchorCreator = action(() => {
+ const note = this.getAnchor(true);
+ if (note && this.selectedPin) {
+ note.latitude = this.selectedPin.latitude;
+ note.longitude = this.selectedPin.longitude;
+ note.map = this.selectedPin.map;
+ }
+ return note as Doc;
+ });
+
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ FormattedTextBox.SetSelectOnLoad(target);
+ return target;
+ };
+ const docView = this.props.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.props.Document;
+ e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ });
+ };
+
+ createNoteAnnotation = () => {
+ const createFunc = undoable(
+ action(() => {
+ const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
+ if (note && this.selectedPin) {
+ note.latitude = this.selectedPin.latitude;
+ note.longitude = this.selectedPin.longitude;
+ note.map = this.selectedPin.map;
+ }
+ }),
+ 'create note annotation'
+ );
+ if (!this.layoutDoc.layout_showSidebar) {
+ this.toggleSidebar();
+ setTimeout(createFunc);
+ } else createFunc();
+ };
+ sidebarDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
+ };
+ sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ const bounds = this._ref.current!.getBoundingClientRect();
+ this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+ this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
+ e.preventDefault();
+ return false;
+ };
+
+ 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);
+
+ pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+ transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
+ infoWidth = () => this.props.PanelWidth() / 5;
+ infoHeight = () => this.props.PanelHeight() / 5;
+ anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+ savedAnnotations = () => this._savedAnnotations;
+
+ _bingSearchManager: any;
+ _bingMap: any;
+ get MicrosoftMaps() {
+ return (window as any).Microsoft.Maps;
+ }
+ // uses Bing Search to retrieve lat/lng for a location. eg.,
+ // const results = this.geocodeQuery(map.map, 'Philadelphia, PA');
+ // to move the map to that location:
+ // const location = await this.geocodeQuery(this._bingMap, 'Philadelphia, PA');
+ // this._bingMap.current.setView({
+ // mapTypeId: this.MicrosoftMaps.MapTypeId.aerial,
+ // 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.
+ if (!this._bingSearchManager) {
+ //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));
+ });
+ } else {
+ this._bingSearchManager.geocode({
+ where: query,
+ callback: action((r: any) => res(r.results[0].location)),
+ errorCallback: (e: any) => reject(),
+ });
+ }
+ });
+ };
+
+ @observable
+ bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
+
+ geoDataRequestOptions = {
+ entityType: 'PopulatedPlace',
+ };
+
+ // incrementer: number = 0;
+ /*
+ * Creates Pushpin doc and adds it to the list of annotations
+ */
+ @action
+ createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
+ // Stores the pushpin as a MapMarkerDocument
+ const pushpin = Docs.Create.PushpinDocument(
+ NumCast(latitude),
+ NumCast(longitude),
+ false,
+ [],
+ { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
+ // ,'pushpinIDamongus'+ this.incrementer++
+ );
+ this.addDocument(pushpin, this.annotationKey);
+ return pushpin;
+ // mapMarker.infoWindowOpen = true;
+ }, 'createpin');
+
+ // The pin that is selected
+ @observable selectedPin: Doc | undefined;
+
+ @action
+ deselectPin = () => {
+ if (this.selectedPin) {
+ // Removes filter
+ Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+ Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+
+ const temp = this.selectedPin;
+ if (!this._unmounting) {
+ 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));
+ if (!this._unmounting) {
+ this._bingMap.current.entities.push(newpin);
+ }
+ this.map_docToPinMap.set(temp, newpin);
+ this.selectedPin = undefined;
+ this.bingSearchBarContents = this.Document.map;
+ }
+ };
+
+ getView = async (doc: Doc) => {
+ if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+ return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ };
+ /*
+ * Pushpin onclick
+ */
+ @action
+ pushpinClicked = (pinDoc: Doc) => {
+ this.deselectPin();
+ this.selectedPin = pinDoc;
+ this.bingSearchBarContents = pinDoc.map;
+
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+
+ this.recolorPin(this.selectedPin, 'green');
+
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+ MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+ MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+ const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
+ const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = point.y + this.props.PanelHeight() / 2 + 32;
+ const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
+
+ document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ };
+
+ /**
+ * Map OnClick
+ */
+ @action
+ mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+ this.props.select(false);
+ this.deselectPin();
+ };
+ /*
+ * Updates values of layout doc to match the current map
+ */
+ @action
+ mapRecentered = () => {
+ if (
+ Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || //
+ Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7
+ ) {
+ this.dataDoc.latitude = this._bingMap.current.getCenter().latitude;
+ this.dataDoc.longitude = this._bingMap.current.getCenter().longitude;
+ this.dataDoc.map = '';
+ this.bingSearchBarContents = '';
+ }
+ this.dataDoc.map_zoom = this._bingMap.current.getZoom();
+ };
+ /*
+ * Updates maptype
+ */
+ @action
+ 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 => {
+ 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
+ */
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
+ /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'MapAnchor:' + this.Document.title,
+ text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
+ config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
+ config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
+ config_map_zoom: NumCast(this.dataDoc.map_zoom),
+ config_map_type: StrCast(this.dataDoc.map_type),
+ config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
+ layout_unrendered: true,
+ mapPin: existingPin ?? this.selectedPin,
+ annotationOn: this.Document,
+ });
+ if (anchor) {
+ if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+ addAsAnnotation && this.addDocument(anchor);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
+ return anchor;
+ }
+ return this.Document;
+ };
+
+ map_docToPinMap = new Map<Doc, any>();
+ map_pinHighlighted = new Map<Doc, boolean>();
+ /*
+ * Input: pin doc
+ * Adds MicrosoftMaps Pushpin to the map (render)
+ */
+ @action
+ addPushpin = (pin: Doc) => {
+ const pushPin = pin.infoWindowOpen
+ ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {})
+ : new this.MicrosoftMaps.Pushpin(
+ new this.MicrosoftMaps.Location(pin.latitude, pin.longitude)
+ // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'}
+ );
+
+ this._bingMap.current.entities.push(pushPin);
+
+ this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+ // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
+ this.map_docToPinMap.set(pin, pushPin);
+ };
+
+ /*
+ * Input: pin doc
+ * Removes pin from annotations
+ */
+ @action
+ removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
+
+ /*
+ * Removes pushpin from map render
+ */
+ deletePushpin = (pinDoc: Doc) => {
+ if (!this._unmounting) {
+ this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
+ }
+ this.map_docToPinMap.delete(pinDoc);
+ this.selectedPin = undefined;
+ };
+
+ @action
+ deleteSelectedPin = undoable(() => {
+ if (this.selectedPin) {
+ // Removes filter
+ Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+ Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+
+ this.removePushpin(this.selectedPin);
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ }, 'delete pin');
+
+ tryHideMapAnchorMenu = (e: PointerEvent) => {
+ let target = document.elementFromPoint(e.x, e.y);
+ while (target) {
+ if (target === MapAnchorMenu.top.current) return;
+ target = target.parentElement;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ };
+
+ @action
+ centerOnSelectedPin = () => {
+ if (this.selectedPin) {
+ this.dataDoc.latitude = this.selectedPin.latitude;
+ this.dataDoc.longitude = this.selectedPin.longitude;
+ this.dataDoc.map = this.selectedPin.map ?? '';
+ this.bingSearchBarContents = this.selectedPin.map;
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
+ };
+
+ /**
+ * View options for bing maps
+ */
+ bingViewOptions = {
+ // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng },
+ zoom: this.dataDoc.latitude ?? 10,
+ mapTypeId: 'grayscale',
+ };
+
+ /**
+ * Map options
+ */
+ bingMapOptions = {
+ navigationBarMode: 'square',
+ backgroundColor: '#f1f3f4',
+ enableInertia: true,
+ supportedMapTypes: ['grayscale', 'canvasLight'],
+ disableMapTypeSelectorMouseOver: true,
+ // showScalebar:true
+ // disableRoadView:true,
+ // disableBirdseye:true
+ streetsideOptions: {
+ showProblemReporting: false,
+ showCurrentAddress: false,
+ },
+ };
+
+ @action
+ 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._bingMap.current.entities.push(newpin);
+ this.map_docToPinMap.set(pin, newpin);
+ };
+
+ /*
+ * Called when BingMap is first rendered
+ * Initializes starting values
+ */
+ @observable _mapReady = false;
+ @action
+ bingMapReady = (map: any) => {
+ this._mapReady = true;
+ this._bingMap = map.map;
+ if (!this._bingMap.current) {
+ alert('NO Map!?');
+ }
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick);
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change'));
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
+
+ this._disposers.mapLocation = reaction(
+ () => this.Document.map,
+ 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 }) => {
+ if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
+ this.recolorPin(pushpin);
+ this.map_pinHighlighted.delete(pushpin);
+ }
+ });
+ allConfigPins.forEach(({ doc, pushpin }) => {
+ if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) {
+ this.recolorPin(pushpin, 'orange');
+ this.map_pinHighlighted.set(pushpin, true);
+ }
+ });
+ },
+ { fireImmediately: true }
+ );
+
+ this._disposers.location = reaction(
+ () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
+ locationObject => {
+ // if (this._bingMap.current)
+ try {
+ locationObject?.zoom &&
+ this._bingMap.current?.setView({
+ mapTypeId: locationObject.mapType,
+ zoom: locationObject.zoom,
+ center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng),
+ });
+ } catch (e) {
+ console.log(e);
+ }
+ },
+ { fireImmediately: true }
+ );
+ };
+
+ dragToggle = (e: React.PointerEvent) => {
+ let dragClone: HTMLDivElement | undefined;
+
+ setupMoveUpEvents(
+ e,
+ e,
+ e => {
+ 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)`;
+ return false;
+ },
+ e => {
+ if (!dragClone) return;
+ DragManager.Root().removeChild(dragClone);
+ let target = document.elementFromPoint(e.x, e.y);
+ while (target) {
+ if (target === this._ref.current) {
+ const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.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));
+ this.createPushpin(location.latitude, location.longitude);
+ break;
+ }
+ target = target.parentElement;
+ }
+ },
+ e => {
+ const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
+ if (this.bingSearchBarContents) {
+ this.bingSearch().then(createPin);
+ } else createPin();
+ }
+ );
+ };
+
+ searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
+
+ static _firstRender = true;
+ static _rerenderDelay = 500;
+ _rerenderTimeout: any;
+ render() {
+ // bcz: no idea what's going on here, but bings maps have some kind of bug
+ // such that we need to delay rendering a second map on startup until the first map is rendered.
+ this.Document[DocCss];
+ if (MapBoxContainer._rerenderDelay) {
+ // prettier-ignore
+ this._rerenderTimeout = this._rerenderTimeout ??
+ setTimeout(action(() => {
+ if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
+ MapBoxContainer._rerenderDelay = 0;
+ }
+ this._rerenderTimeout = undefined;
+ this.Document[DocCss] = this.Document[DocCss] + 1;
+ }), MapBoxContainer._rerenderDelay);
+ return null;
+ }
+
+ const renderAnnotations = (childFilters?: () => string[]) => null;
+ return (
+ <div className="mapBox" ref={this._ref}>
+ <div
+ className="mapBox-wrapper"
+ onWheel={e => e.stopPropagation()}
+ onPointerDown={async e => {
+ 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()}
+ placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
+ textAlign="center"
+ />
+ <IconButton
+ icon={
+ <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>
+ </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'} />} />
+ </div>
+ </div>
+ <MapProvider>
+ <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} />
+ </MapProvider>
+
+ {/*
+ <BingMapsReact
+ onMapReady={this.bingMapReady} //
+ bingMapsKey={bingApiKey}
+ height="100%"
+ mapOptions={this.bingMapOptions}
+ width="100%"
+ viewOptions={this.bingViewOptions}
+ /> */}
+ <div>
+ {!this._mapReady
+ ? null
+ : this.allAnnotations
+ .filter(anno => !anno.layout_unrendered)
+ .map((pushpin, i) => (
+ <DocumentView
+ key={i}
+ {...this.props}
+ renderDepth={this.props.renderDepth + 1}
+ Document={pushpin}
+ PanelWidth={returnOne}
+ PanelHeight={returnOne}
+ NativeWidth={returnOne}
+ NativeHeight={returnOne}
+ onKey={undefined}
+ onDoubleClick={undefined}
+ onBrowseClick={undefined}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ isDocumentActive={returnFalse}
+ isContentActive={returnFalse}
+ addDocTab={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ fitContentsToBox={undefined}
+ focus={returnOne}
+ />
+ ))}
+ </div>
+ {/* <MapBoxInfoWindow
+ key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
+ markerMap={this.markerMap}
+ PanelWidth={this.infoWidth}
+ PanelHeight={this.infoHeight}
+ moveDocument={this.moveDocument}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ /> */}
+ </div>
+ {/* </LoadScript > */}
+ <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <SidebarAnnos
+ ref={this._sidebarRef}
+ {...this.props}
+ fieldKey={this.fieldKey}
+ Document={this.Document}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ usePanelWidth={true}
+ showSidebar={this.SidebarShown}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ PanelWidth={this.sidebarWidth}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.sidebarRemoveDocument}
+ />
+ </div>
+ {this.sidebarHandle}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 8a68f9647..0f5e25a0c 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
.pdfBox,
.pdfBox-interactive {
@@ -38,7 +38,7 @@
box-shadow: $standard-box-shadow;
transition: 0.2s;
- &:hover{
+ &:hover {
filter: brightness(0.85);
}
}
@@ -51,7 +51,8 @@
left: 5px;
top: 5px;
- .pdfBox-fwdBtn, .pdfBox-backBtn {
+ .pdfBox-fwdBtn,
+ .pdfBox-backBtn {
background: #121721;
height: 25px;
width: 25px;
@@ -119,7 +120,6 @@
background: none;
}
-
.pdfBox-settingsCont {
position: absolute;
right: 0;
@@ -194,7 +194,7 @@
justify-content: center;
align-items: center;
overflow: hidden;
- transition: left .5s;
+ transition: left 0.5s;
pointer-events: all;
.pdfBox-searchBar {
@@ -204,7 +204,6 @@
}
}
-
.pdfBox-title-outer {
width: 100%;
height: 100%;
@@ -269,7 +268,6 @@
// CSS adjusted for mobile devices
@media only screen and (max-device-width: 480px) {
-
.pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton,
.pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton {
height: 60px;
@@ -288,15 +286,11 @@
}
}
-
-
.pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout,
.pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout {
font-size: 30px;
}
-
-
.pdfBox .pdfBox-ui .pdfBox-overlayCont,
.pdfBox-interactive .pdfBox-ui .pdfBox-overlayCont {
height: 60px;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 108fa5ce5..733febd2d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,8 +1,9 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+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 { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -31,7 +32,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './PDFBox.scss';
import { PinProps, PresBox } from './trails';
-import React = require('react');
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -52,7 +52,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _pageControls = false;
@computed get pdfUrl() {
- return Cast(this.dataDoc[this.props.fieldKey], PdfField);
+ return Cast(this.dataDoc[this._props.fieldKey], PdfField);
}
@computed get pdfThumb() {
return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
@@ -60,6 +60,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
constructor(props: any) {
super(props);
+ makeObservable(this);
const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
!this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
@@ -97,11 +98,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
+ Doc.GetProto(region).title = 'region:' + this.Document.title;
Doc.GetProto(region).followLinkToggle = true;
this.addDocument(region);
- const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
newDiv.style.width = NumCast(this.layoutDoc._width).toString();
newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -110,19 +111,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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 anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
+ const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
const viewScale = 1;
- cropping.title = 'crop: ' + this.rootDoc.title;
- cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
- cropping.y = NumCast(this.rootDoc.y);
+ cropping.title = 'crop: ' + this.Document.title;
+ cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
+ cropping.y = NumCast(this.Document.y);
cropping._width = anchw;
cropping._height = anchh;
cropping.onClick = undefined;
const croppingProto = Doc.GetProto(cropping);
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
- croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ 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'));
@@ -131,7 +132,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
CreateImage(
'',
@@ -139,8 +140,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
htmlString,
anchw,
anchh,
- (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']),
- (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']),
+ (NumCast(region.y) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
+ (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
4
)
.then((data_url: any) => {
@@ -162,7 +163,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
updateIcon = () => {
// currently we render pdf icons as text labels
- const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
this._pdfViewer?._mainCont.current &&
CollectionFreeFormView.UpdateIcon(
@@ -170,10 +171,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
docViewContent,
NumCast(this.layoutDoc._width),
NumCast(this.layoutDoc._height),
- this.props.PanelWidth(),
- this.props.PanelHeight(),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
NumCast(this.layoutDoc._layout_scrollTop),
- NumCast(this.rootDoc[this.fieldKey + '_nativeHeight'], 1),
+ NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], 1),
true,
this.layoutDoc[Id] + '-icon',
(iconFile: string, nativeWidth: number, nativeHeight: number) => {
@@ -190,33 +191,33 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
Object.values(this._disposers).forEach(disposer => disposer?.());
}
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
this._disposers.select = reaction(
- () => this.props.isSelected(),
+ () => this._props.isSelected(),
() => {
document.removeEventListener('keydown', this.onKeyDown);
- this.props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
+ this._props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
},
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
- () => this.rootDoc.layout_scrollTop,
+ () => this.layoutDoc.layout_scrollTop,
() => {
- if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
- this.props.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
+ if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
+ this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
}
- this.props.Document[this.SidebarKey + '_freeform_scale'] = 1;
- this.props.Document[this.SidebarKey + '_freeform_panX'] = 0;
+ this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
+ this.layoutDoc[this.SidebarKey + '_freeform_panX'] = 0;
}
);
}
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
- if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+ if (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(doc, where);
};
focus = (anchor: Doc, options: DocFocusOptions) => {
this._initialScrollTarget = anchor;
@@ -236,12 +237,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
const docAnchor = () =>
Docs.Create.ConfigDocument({
- title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
- annotationOn: this.rootDoc,
+ title: StrCast(this.Document.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
+ annotationOn: this.Document,
});
const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
const anchor = visibleAnchor ?? docAnchor();
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.Document);
anchor.text = ele?.textContent ?? '';
anchor.text_html = ele?.innerHTML;
addAsAnnotation && this.addDocument(anchor);
@@ -251,7 +252,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
loaded = (nw: number, nh: number, np: number) => {
- this.dataDoc[this.props.fieldKey + '_numPages'] = np;
+ this.dataDoc[this._props.fieldKey + '_numPages'] = np;
Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
@@ -276,7 +277,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return true;
};
public forwardPage = () => {
- this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
+ 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);
@@ -300,7 +301,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setPdfViewer = (pdfViewer: PDFViewer) => {
this._pdfViewer = pdfViewer;
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (this._initialScrollTarget && docView) {
this.focus(this._initialScrollTarget, { instant: true });
this._initialScrollTarget = undefined;
@@ -321,13 +322,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this,
e,
(e, down, delta) => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+ const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth;
if (ratio >= 1) {
this.layoutDoc.nativeWidth = nativeWidth * ratio;
onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
@@ -372,12 +373,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`;
const curPage = NumCast(this.Document._layout_curPage) || 1;
- return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
+ return !this._props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
<div
className="pdfBox-ui"
onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)}
onPointerDown={e => e.stopPropagation()}
- style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+ style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
<input
@@ -431,10 +432,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this.SidebarShown) return 0;
if (this._previewWidth) return PDFBox.sidebarResizerWidth + PDFBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
- return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
+ return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
};
@undoBatch
- toggleSidebarType = () => (this.rootDoc[this.SidebarKey + '_type_collection'] = this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
+ toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
@@ -450,11 +451,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@computed get renderTitleBox() {
- const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : '');
+ const classname = 'pdfBox' + (this._props.isContentActive() ? '-interactive' : '');
return (
<div className={classname}>
<div className="pdfBox-title-outer">
- <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong>
+ <strong className="pdfBox-title">{StrCast(this.Document.title)}</strong>
</div>
</div>
);
@@ -472,8 +473,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
- top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+ display: !this._props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
@@ -489,27 +490,27 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
const pdfRatio = pdfNativeWidth / nativeWidth;
- return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
+ return (pdfRatio * this._props.PanelWidth()) / pdfNativeWidth;
}
@computed get sidebarNativeWidth() {
return this.sidebarWidth() / this.pdfScale;
}
@computed get sidebarNativeHeight() {
- return this.props.PanelHeight() / this.pdfScale;
+ return this._props.PanelHeight() / this.pdfScale;
}
sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
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);
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
+ sidebarScreenToLocal = () => this._props.ScreenToLocalTransform().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
- rootDoc={this.rootDoc}
+ {...this._props}
+ Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
setHeight={emptyFunction}
@@ -521,13 +522,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...this.props}
+ {...this._props}
setContentView={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
NativeHeight={this.sidebarNativeHeightFunc}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
@@ -541,7 +542,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
moveDocument={this.sidebarMoveDocument}
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
noSidebar={true}
fieldKey={this.SidebarKey}
/>
@@ -557,13 +558,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get renderPdfView() {
TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return !this._pdf ? null : (
<div
className="pdfBox"
onContextMenu={this.specificContextMenu}
style={{
- height: this.props.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
+ height: this.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this._props.PanelWidth()) / NumCast(this.Document._width) : undefined,
}}>
<div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
<div
@@ -576,9 +577,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
top: 0,
}}>
<PDFViewer
- {...this.props}
+ {...this._props}
sidebarAddDoc={this.sidebarAddDocument}
- rootDoc={this.rootDoc}
addDocTab={this.sidebarAddDocTab}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
@@ -595,7 +595,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
crop={this.crop}
/>
</div>
- <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
+ <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
{this.settingsPanel()}
</div>
);
@@ -605,8 +605,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
render() {
TraceMobx();
- const pdfView = this.renderPdfView;
-
+ 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))));
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index cd1ff17dd..e75b1ab6f 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -15,11 +15,11 @@ import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from './../FieldView';
import './PhysicsSimulationBox.scss';
import InputField from './PhysicsSimulationInputField';
-import * as questions from './PhysicsSimulationQuestions.json';
-import * as tutorials from './PhysicsSimulationTutorial.json';
+import questions from './PhysicsSimulationQuestions.json';
+import tutorials from './PhysicsSimulationTutorial.json';
import Wall from './PhysicsSimulationWall';
import Weight from './PhysicsSimulationWeight';
-import React = require('react');
+import * as React from 'react';
interface IWallProps {
length: number;
@@ -202,7 +202,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
];
}
- componentDidUpdate() {
+ componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<FieldViewProps>>) {
+ super.componentDidUpdate(prevProps);
if (this.xMax !== this.props.PanelWidth() * 0.6 || this.yMax != this.props.PanelHeight()) {
this.xMax = this.props.PanelWidth() * 0.6;
this.yMax = this.props.PanelHeight();
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx
index d595a499e..c704863f5 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx
@@ -1,6 +1,6 @@
import { TextField, InputAdornment } from '@mui/material';
import { Doc } from '../../../../fields/Doc';
-import React = require('react');
+import * as React from 'react';
import TaskAltIcon from '@mui/icons-material/TaskAlt';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { isNumber } from 'lodash';
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx
index 8cc1d0fbf..696352296 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx
@@ -1,34 +1,33 @@
-import React = require('react');
+import * as React from 'react';
export interface Force {
- magnitude: number;
- directionInDegrees: number;
+ magnitude: number;
+ directionInDegrees: number;
}
export interface IWallProps {
- length: number;
- xPos: number;
- yPos: number;
- angleInDegrees: number;
+ length: number;
+ xPos: number;
+ yPos: number;
+ angleInDegrees: number;
}
export default class Wall extends React.Component<IWallProps> {
+ constructor(props: any) {
+ super(props);
+ }
- constructor(props: any) {
- super(props)
- }
+ wallStyle = {
+ width: this.props.angleInDegrees == 0 ? this.props.length + '%' : '5px',
+ height: this.props.angleInDegrees == 0 ? '5px' : this.props.length + '%',
+ position: 'absolute' as 'absolute',
+ left: this.props.xPos + '%',
+ top: this.props.yPos + '%',
+ backgroundColor: '#6c7b8b',
+ margin: 0,
+ padding: 0,
+ };
- wallStyle = {
- width: this.props.angleInDegrees == 0 ? this.props.length + "%" : "5px",
- height: this.props.angleInDegrees == 0 ? "5px" : this.props.length + "%",
- position: "absolute" as "absolute",
- left: this.props.xPos + "%",
- top: this.props.yPos + "%",
- backgroundColor: "#6c7b8b",
- margin: 0,
- padding: 0,
- };
-
- render () {
- return (<div style={this.wallStyle}></div>);
- }
-};
+ render() {
+ return <div style={this.wallStyle}></div>;
+ }
+}
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index 2165c8ba9..f5077a07e 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -1,7 +1,7 @@
-import { computed, IReactionDisposer, reaction } from 'mobx';
+import { computed, IReactionDisposer, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import './PhysicsSimulationBox.scss';
-import React = require('react');
+import * as React from 'react';
interface IWallProps {
length: number;
@@ -93,6 +93,7 @@ interface IState {
export default class Weight extends React.Component<IWeightProps, IState> {
constructor(props: any) {
super(props);
+ makeObservable(this);
this.state = {
angleLabel: 0,
clickPositionX: 0,
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
index 312b51013..9cd1ee23a 100644
--- a/src/client/views/nodes/RadialMenu.scss
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
.radialMenu-cont {
position: absolute;
@@ -26,7 +26,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
white-space: nowrap;
font-size: 13px;
@@ -34,8 +34,7 @@
text-transform: uppercase;
}
-s
-.radialMenu-itemSelected {
+s .radialMenu-itemSelected {
border-style: none;
}
@@ -50,8 +49,8 @@ s
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
- border-width: .11px;
+ transition: all 0.1s;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray; // rgb(187, 186, 186);
// padding: 10px 0px 10px 0px;
@@ -62,9 +61,8 @@ s
padding-left: 5px;
}
-
.radialMenu-description {
margin-left: 5px;
text-align: left;
display: inline; //need this?
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 7f0956e51..061a46f03 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,17 +1,17 @@
-import React = require("react");
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import "./RadialMenu.scss";
-import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
+import * as React from 'react';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import './RadialMenu.scss';
+import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
@observer
export class RadialMenu extends React.Component {
static Instance: RadialMenu;
static readonly buffer = 20;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
-
+ makeObservable(this);
RadialMenu.Instance = this;
}
@@ -23,11 +23,10 @@ export class RadialMenu extends React.Component {
public used: boolean = false;
-
catchTouch = (te: React.TouchEvent) => {
te.stopPropagation();
te.preventDefault();
- }
+ };
@action
onPointerDown = (e: PointerEvent) => {
@@ -35,8 +34,8 @@ export class RadialMenu extends React.Component {
this._mouseX = e.clientX;
this._mouseY = e.clientY;
this.used = false;
- document.addEventListener("pointermove", this.onPointerMove);
- }
+ document.addEventListener('pointermove', this.onPointerMove);
+ };
@observable
private _closest: number = -1;
@@ -60,11 +59,10 @@ export class RadialMenu extends React.Component {
}
}
this._closest = closest;
- }
- else {
+ } else {
this._closest = -1;
}
- }
+ };
@action
onPointerUp = (e: PointerEvent) => {
this.used = true;
@@ -75,43 +73,41 @@ export class RadialMenu extends React.Component {
this._shouldDisplay = false;
}
this._shouldDisplay && (this._display = true);
- document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener('pointermove', this.onPointerMove);
if (this._closest !== -1 && this._items?.length > this._closest) {
this._items[this._closest].event();
}
- }
+ };
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onPointerDown);
+ document.removeEventListener('pointerdown', this.onPointerDown);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointerup', this.onPointerUp);
this._reactionDisposer && this._reactionDisposer();
}
@action
componentDidMount = () => {
- document.addEventListener("pointerdown", this.onPointerDown);
- document.addEventListener("pointerup", this.onPointerUp);
+ 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)
+ () => 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;
}
@@ -133,7 +129,7 @@ export class RadialMenu extends React.Component {
this._mouseX = x;
this._mouseY = y;
this._shouldDisplay = true;
- }
+ };
// @computed
// get pageX() {
// const x = this._pageX;
@@ -168,7 +164,7 @@ export class RadialMenu extends React.Component {
this.clearItems();
this._display = false;
this._shouldDisplay = false;
- }
+ };
@action
openMenu = (x: number, y: number) => {
@@ -176,56 +172,52 @@ export class RadialMenu extends React.Component {
this._pageY = y;
this._shouldDisplay;
this._display = true;
- }
+ };
@action
clearItems() {
this._items = [];
}
-
previewcircle() {
- if (document.getElementById("newCanvas") !== null) {
- const c: any = document.getElementById("newCanvas");
+ if (document.getElementById('newCanvas') !== null) {
+ const c: any = document.getElementById('newCanvas');
if (c.getContext) {
- const ctx = c.getContext("2d");
+ const ctx = c.getContext('2d');
ctx.beginPath();
ctx.arc(150, 150, 50, 0, 2 * Math.PI);
- ctx.fillStyle = "white";
+ ctx.fillStyle = 'white';
ctx.fill();
- ctx.font = "12px Arial";
- ctx.fillStyle = "black";
- ctx.textAlign = "center";
- let description = "";
+ ctx.font = '12px Arial';
+ ctx.fillStyle = 'black';
+ ctx.textAlign = 'center';
+ let description = '';
if (this._closest !== -1) {
description = this._items[this._closest].description;
}
if (description.length > 15) {
description = description.slice(0, 12);
- description += "...";
+ description += '...';
}
ctx.fillText(description, 150, 150, 90);
}
}
}
-
render() {
if (!this._display) {
return null;
}
- const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } :
- { left: this._pageX - 130, top: this._pageY - 130 };
+ const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : { left: this._pageX - 130, top: this._pageY - 130 };
return (
-
<div className="radialMenu-cont" onTouchStart={this.catchTouch} style={style}>
- <canvas id="newCanvas" style={{ position: "absolute" }} height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas>
+ <canvas id="newCanvas" style={{ position: 'absolute' }} height="300" width="300">
+ {' '}
+ Your browser does not support the HTML5 canvas tag.
+ </canvas>
{this.menuItems}
</div>
-
);
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index 8876b4879..c931202f1 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,8 +1,8 @@
-import React = require("react");
+import * as React from 'react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { observer } from "mobx-react";
-import { UndoManager } from "../../util/UndoManager";
+import { observer } from 'mobx-react';
+import { UndoManager } from '../../util/UndoManager';
export interface RadialMenuProps {
description: string;
@@ -15,17 +15,15 @@ export interface RadialMenuProps {
selected: number;
}
-
@observer
export class RadialMenuItem extends React.Component<RadialMenuProps> {
-
componentDidMount = () => {
this.setcircle();
- }
+ };
componentDidUpdate = () => {
this.setcircle();
- }
+ };
handleEvent = async (e: React.PointerEvent) => {
this.props.closeMenu && this.props.closeMenu();
@@ -35,38 +33,36 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
}
await this.props.event({ x: e.clientX, y: e.clientY });
batch && batch.end();
- }
-
+ };
setcircle() {
let circlemin = 0;
let circlemax = 1;
- this.props.min ? circlemin = this.props.min : null;
- this.props.max ? circlemax = this.props.max : null;
- if (document.getElementById("myCanvas") !== null) {
- const c: any = document.getElementById("myCanvas");
- let color = "white";
+ this.props.min ? (circlemin = this.props.min) : null;
+ this.props.max ? (circlemax = this.props.max) : null;
+ if (document.getElementById('myCanvas') !== null) {
+ const c: any = document.getElementById('myCanvas');
+ let color = 'white';
switch (circlemin % 3) {
case 1:
- color = "#c2c2c5";
+ color = '#c2c2c5';
break;
case 0:
- color = "#f1efeb";
+ color = '#f1efeb';
break;
case 2:
- color = "lightgray";
+ color = 'lightgray';
break;
}
if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
- color = "#c2c2c5";
+ color = '#c2c2c5';
}
if (this.props.selected === this.props.min) {
- color = "#808080";
-
+ color = '#808080';
}
if (c.getContext) {
- const ctx = c.getContext("2d");
+ const ctx = c.getContext('2d');
ctx.beginPath();
ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI);
ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true);
@@ -79,35 +75,36 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
calculatorx() {
let circlemin = 0;
let circlemax = 1;
- this.props.min ? circlemin = this.props.min : null;
- this.props.max ? circlemax = this.props.max : null;
- const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+ this.props.min ? (circlemin = this.props.min) : null;
+ this.props.max ? (circlemax = this.props.max) : null;
+ 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);
+ const x = 100 * Math.cos((degrees * Math.PI) / 180);
+ const y = -125 * Math.sin((degrees * Math.PI) / 180);
return x;
}
calculatory() {
-
let circlemin = 0;
let circlemax = 1;
- this.props.min ? circlemin = this.props.min : null;
- this.props.max ? circlemax = this.props.max : null;
- const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+ this.props.min ? (circlemin = this.props.min) : null;
+ 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);
+ const x = 125 * Math.cos((degrees * Math.PI) / 180);
+ const y = -100 * Math.sin((degrees * Math.PI) / 180);
return y;
}
-
render() {
return (
- <div className={"radialMenu-item" + (this.props.selected ? " radialMenu-itemSelected" : "")} onPointerUp={this.handleEvent}>
- <canvas id="myCanvas" height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas>
- <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: "absolute", left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} />
+ <div className={'radialMenu-item' + (this.props.selected ? ' radialMenu-itemSelected' : '')} onPointerUp={this.handleEvent}>
+ <canvas id="myCanvas" height="300" width="300">
+ {' '}
+ Your browser does not support the HTML5 canvas tag.
+ </canvas>
+ <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: 'absolute', left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} />
</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 116069cbd..f01642236 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -13,7 +13,6 @@ import { DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
-import { SelectionManager } from '../../../util/SelectionManager';
import { Presentation } from '../../../util/TrackMovements';
import { undoBatch } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
@@ -62,7 +61,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
public static WorkspaceStopRecording() {
- const remDoc = RecordingBox.screengrabber?.rootDoc;
+ 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
RecordingBox.screengrabber?.Pause?.();
@@ -122,7 +121,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
value.overlayY = window.innerHeight - 180;
Doc.AddToMyOverlay(value);
DocumentManager.Instance.AddViewRenderedCb(value, docView => {
- Doc.UserDoc().currentRecording = docView.rootDoc;
+ Doc.UserDoc().currentRecording = docView.Document;
docView.select(false);
RecordingBox.resumeWorkspaceReplaying(value);
});
@@ -136,9 +135,9 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
public static addRecToWorkspace(value: RecordingBox) {
let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
- (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.rootDoc);
- Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.rootDoc);
- Doc.RemFromMyOverlay(value.rootDoc);
+ (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.Document);
+ Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
+ Doc.RemFromMyOverlay(value.Document);
Doc.UserDoc().currentRecording = undefined;
Doc.UserDoc().workspaceReplayingState = undefined;
Doc.UserDoc().workspaceRecordingState = undefined;
@@ -193,14 +192,14 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
return (
- <div className="recordingBox" ref={this._ref}>
+ <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.rootDoc.proto)?.[Id] || ''}
+ id={DocCast(this.Document.proto)?.[Id] || ''}
/>
)}
</div>
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index b2512a0e5..d5d31b407 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,7 +1,7 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// import { Canvas } from '@react-three/fiber';
-import { computed, observable, runInAction } from 'mobx';
+import { computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
import { DateField } from '../../../fields/DateField';
@@ -37,13 +37,13 @@ declare class MediaRecorder {
// setRaised: (r: { coord: Vector2, off: Vector3 }[]) => void;
// x: number;
// y: number;
-// rootDoc: Doc;
+// doc: Doc;
// color: string;
// }
// @observer
// export class VideoTile extends React.Component<VideoTileProps> {
-// @observable _videoRef: HTMLVideoElement | undefined;
+// @observable _videoRef: HTMLVideoElement | undefined = undefined;
// _mesh: any = undefined;
// render() {
@@ -69,8 +69,8 @@ declare class MediaRecorder {
// const normals = new Float32Array(quad_normals);
// const uvs = new Float32Array(quad_uvs); // Each vertex has one uv coordinate for texture mapping
// const indices = new Uint32Array(quad_indices); // Use the four vertices to draw the two triangles that make up the square.
-// const popOut = () => NumCast(this.props.rootDoc.popOut);
-// const popOff = () => NumCast(this.props.rootDoc.popOff);
+// const popOut = () => NumCast(this.props.Document.popOut);
+// const popOff = () => NumCast(this.props.Document.popOff);
// return (
// <mesh key={`mesh${topLeft[0]}${topLeft[1]}`} onClick={action(async e => {
// this.props.setRaised([
@@ -123,18 +123,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
constructor(props: any) {
super(props);
- if (this.rootDoc.videoWall) {
- this.rootDoc.nativeWidth = undefined;
- this.rootDoc.nativeHeight = undefined;
- this.layoutDoc.popOff = 0;
- this.layoutDoc.popOut = 1;
- } else {
- this.setupDictation();
- }
+ makeObservable(this);
+ this.setupDictation();
}
getAnchor = (addAsAnnotation: boolean) => {
const startTime = Cast(this.layoutDoc._layout_currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc;
+ return CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.Document;
};
videoLoad = () => {
@@ -151,7 +145,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
componentDidMount() {
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
this.props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- // this.rootDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }),
+ // this.layoutDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }),
// ({ width, height }) => {
// if (this._camera) {
// const angle = -Math.abs(1 - width / height);
@@ -173,7 +167,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
@computed get content() {
- if (this.rootDoc.videoWall) return null;
return (
<video
className={'videoBox-content'}
@@ -181,7 +174,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
ref={r => {
this._videoRef = r;
setTimeout(() => {
- if (this.rootDoc.mediaState === media_state.PendingRecording && this._videoRef) {
+ if (this.layoutDoc.mediaState === media_state.PendingRecording && this._videoRef) {
this.toggleRecording();
}
}, 100);
@@ -202,12 +195,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// @observable _raised = [] as { coord: Vector2, off: Vector3 }[];
// @action setRaised = (r: { coord: Vector2, off: Vector3 }[]) => this._raised = r;
@computed get threed() {
- // if (this.rootDoc.videoWall) {
+ // if (this.layoutDoc.videoWall) {
// const screens: any[] = [];
// const colors = ["yellow", "red", "orange", "brown", "maroon", "gray"];
// let count = 0;
// numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push(
- // <VideoTile rootDoc={this.rootDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />)));
+ // <VideoTile doc={this.layoutDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />)));
// return <Canvas key="canvas" id="CANCAN" style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }} gl={{ antialias: false }} colorManagement={false} onCreated={props => {
// this._camera = props.camera;
// props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2);
@@ -250,7 +243,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
}
TrackMovements.Instance.finish();
- const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
+ const file = new File(vid_chunks, `${this.Document[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
const [{ result }] = await Networking.UploadFilesToServer({ file });
this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
if (!(result instanceof Error)) {
@@ -279,19 +272,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
- CaptureManager.Instance.open(this.rootDoc);
+ CaptureManager.Instance.open(this.Document);
}
};
setupDictation = () => {
if (this.dataDoc[this.fieldKey + '_dictation']) return;
- const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
+ const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
const textField = Doc.LayoutFieldKey(dictationText);
dictationText._layout_autoHeight = false;
const dictationTextProto = Doc.GetProto(dictationText);
dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
- dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
- dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
+ dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+ dictationTextProto.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
};
videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth();
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
index 8d76f2b1d..9789da55a 100644
--- a/src/client/views/nodes/ScriptingBox.scss
+++ b/src/client/views/nodes/ScriptingBox.scss
@@ -32,7 +32,7 @@
.scriptingBox-wrapper {
width: 100%;
height: 100%;
- max-height: calc(100%-30px);
+ max-height: calc(100% - 30px);
display: flex;
flex-direction: row;
overflow: auto;
@@ -42,7 +42,8 @@
overflow: hidden;
}
- .scriptingBox-textArea, .scriptingBox-textArea-inputs {
+ .scriptingBox-textArea,
+ .scriptingBox-textArea-inputs {
flex: 70;
height: 100%;
max-width: 95%;
@@ -110,7 +111,7 @@
cursor: pointer;
}
- .rta__entity>* {
+ .rta__entity > * {
padding-left: 4px;
padding-right: 4px;
}
@@ -125,7 +126,7 @@
.scriptingBox-textArea-inputs {
max-width: 100%;
height: 40%;
- width: 100%;
+ width: 100%;
resize: none;
}
.scriptingBox-textArea-script {
@@ -194,8 +195,8 @@
.scriptingBox-errorMessage {
overflow: auto;
- background: "red";
- background-color: "red";
+ background: 'red';
+ background-color: 'red';
height: 45px;
}
@@ -221,4 +222,4 @@
width: 25%;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 197c520c7..7e7eaee45 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,5 +1,5 @@
let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../../fields/Doc';
@@ -60,8 +60,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
constructor(props: any) {
super(props);
+ makeObservable(this);
if (!this.compileParams.length) {
- const params = ScriptCast(this.rootDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
+ const params = ScriptCast(this.dataDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
if (params) {
this.compileParams = Array.from(Object.keys(params))
.filter(p => !p.startsWith('_'))
@@ -78,30 +79,30 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
return this.compileParams.map(p => p.split(':')[1].trim());
}
@computed({ keepAlive: true }) get rawScript() {
- return ScriptCast(this.rootDoc[this.fieldKey])?.script.originalScript ?? '';
+ return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? '';
}
@computed({ keepAlive: true }) get functionName() {
- return StrCast(this.rootDoc[this.props.fieldKey + '-functionName'], '');
+ return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
}
@computed({ keepAlive: true }) get functionDescription() {
- return StrCast(this.rootDoc[this.props.fieldKey + '-functionDescription'], '');
+ return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
}
@computed({ keepAlive: true }) get compileParams() {
- return Cast(this.rootDoc[this.props.fieldKey + '-params'], listSpec('string'), []);
+ return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
}
set rawScript(value) {
- Doc.SetInPlace(this.rootDoc, this.props.fieldKey, new ScriptField(undefined, undefined, value), true);
+ this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value);
}
set functionName(value) {
- Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionName', value, true);
+ this.dataDoc[this.fieldKey + '-functionName'] = value;
}
set functionDescription(value) {
- Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionDescription', value, true);
+ this.dataDoc[this.fieldKey + '-functionDescription'] = value;
}
set compileParams(value) {
- Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-params', new List<string>(value), true);
+ this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
}
getValue(result: any, descrip: boolean) {
@@ -165,9 +166,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// only included in buttons, transforms scripting UI to a button
@action
- onFinish = () => {
- this.rootDoc.layout_fieldKey = 'layout';
- };
+ onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout');
// displays error message
@action
@@ -189,7 +188,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
params,
typecheck: false,
});
- Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : undefined, true);
+ this.dataDoc[this.fieldKey] = result.compiled ? new ScriptField(result, undefined, this.rawText) : undefined;
this.onError(result.compiled ? undefined : result.errors);
return result.compiled;
};
@@ -199,9 +198,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
onRun = () => {
if (this.onCompile()) {
const bindings: { [name: string]: any } = {};
- this.paramsNames.forEach(key => (bindings[key] = this.rootDoc[key]));
- // binds vars so user doesnt have to refer to everything as self.<var>
- ScriptCast(this.rootDoc[this.fieldKey], null)?.script.run({ ...bindings, self: this.rootDoc, this: this.layoutDoc }, this.onError);
+ 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);
}
};
@@ -270,7 +269,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
@action
onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
if (de.complete.docDragData) {
- de.complete.docDragData.droppedDocuments.forEach(doc => Doc.SetInPlace(this.rootDoc, fieldKey, doc, true));
+ de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc));
e.stopPropagation();
return true;
}
@@ -280,7 +279,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// deletes a param from all areas in which it is stored
@action
onDelete = (num: number) => {
- Doc.SetInPlace(this.rootDoc, this.paramsNames[num], undefined, true);
+ this.dataDoc[this.paramsNames[num]] = undefined;
this.compileParams.splice(num, 1);
return true;
};
@@ -290,13 +289,13 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
viewChanged = (e: React.ChangeEvent, name: string) => {
//@ts-ignore
const val = e.target.selectedOptions[0].value;
- Doc.SetInPlace(this.rootDoc, name, val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true', true);
+ this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
};
// creates a copy of the script document
onCopy = () => {
- const copy = Doc.MakeCopy(this.rootDoc, true);
- copy.x = NumCast(this.dataDoc.x) + NumCast(this.dataDoc._width);
+ const copy = Doc.MakeCopy(this.Document, true);
+ copy.x = NumCast(this.Document.x) + NumCast(this.dataDoc._width);
this.props.addDocument?.(copy);
};
@@ -346,8 +345,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
maxHeight={72}
height={35}
fontSize={14}
- contents={StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
- GetValue={() => StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
+ contents={StrCast(DocCast(this.dataDoc[parameter])?.title, 'undefined')}
+ GetValue={() => StrCast(DocCast(this.dataDoc[parameter])?.title, 'undefined')}
SetValue={action((value: string) => {
const script = CompileScript(value, {
addReturn: true,
@@ -357,7 +356,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
const results = script.compiled && script.run();
if (results && results.success) {
this._errorMessage = '';
- Doc.SetInPlace(this.rootDoc, parameter, results.result, true);
+ this.dataDoc[parameter] = results.result;
return true;
}
this._errorMessage = 'invalid document';
@@ -370,7 +369,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// rendering when a string's value can be set in applied UI
renderBasicType(parameter: string, isNum: boolean) {
- const strVal = isNum ? NumCast(this.rootDoc[parameter]).toString() : StrCast(this.rootDoc[parameter]);
+ const strVal = isNum ? NumCast(this.dataDoc[parameter]).toString() : StrCast(this.dataDoc[parameter]);
return (
<div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
<EditableView
@@ -384,7 +383,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
const setValue = isNum ? parseInt(value) : value;
if (setValue !== undefined && setValue !== ' ') {
this._errorMessage = '';
- Doc.SetInPlace(this.rootDoc, parameter, setValue, true);
+ this.dataDoc[parameter] = setValue;
return true;
}
this._errorMessage = 'invalid input';
@@ -405,7 +404,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
className="scriptingBox-viewPicker"
onPointerDown={e => e.stopPropagation()}
onChange={e => this.viewChanged(e, parameter)}
- value={typeof this.rootDoc[parameter] === 'string' ? 'S' + StrCast(this.rootDoc[parameter]) : typeof this.rootDoc[parameter] === 'number' ? 'N' + NumCast(this.rootDoc[parameter]) : 'B' + BoolCast(this.rootDoc[parameter])}>
+ 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) => (
<option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
{' '}
@@ -703,7 +702,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// toolbar (with compile and apply buttons) for scripting UI
renderScriptingTools() {
- const buttonStyle = 'scriptingBox-button' + (StrCast(this.rootDoc.layout_fieldKey).startsWith('layout_on') ? '-finish' : '');
+ const buttonStyle = 'scriptingBox-button' + (StrCast(this.Document.layout_fieldKey).startsWith('layout_on') ? '-finish' : '');
return (
<div className="scriptingBox-toolbar">
<button
@@ -731,7 +730,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
Save
</button>
- {!StrCast(this.rootDoc.layout_fieldKey).startsWith('layout_on') ? null : ( // onClick, onChecked, etc need a Finish button to return to their default layout
+ {!StrCast(this.Document.layout_fieldKey).startsWith('layout_on') ? null : ( // onClick, onChecked, etc need a Finish button to return to their default layout
<button
className={buttonStyle}
onPointerDown={e => {
@@ -748,7 +747,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// inputs UI for params which allows you to set values for each displayed in a list
renderParamsInputs() {
return (
- <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()}>
+ <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
{!this.compileParams.length || !this.paramsNames ? null : (
<div className="scriptingBox-plist">
{this.paramsNames.map((parameter: string, i: number) => (
@@ -777,7 +776,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// toolbar (with edit and run buttons and error message) for params UI
renderTools(toolSet: string, func: () => void) {
- const buttonStyle = 'scriptingBox-button' + (StrCast(this.rootDoc.layout_fieldKey).startsWith('layout_on') ? '-finish' : '');
+ const buttonStyle = 'scriptingBox-button' + (StrCast(this.Document.layout_fieldKey).startsWith('layout_on') ? '-finish' : '');
return (
<div className="scriptingBox-toolbar">
<button
@@ -796,7 +795,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
}}>
{toolSet}
</button>
- {!StrCast(this.rootDoc.layout_fieldKey).startsWith('layout_on') ? null : (
+ {!StrCast(this.Document.layout_fieldKey).startsWith('layout_on') ? null : (
<button
className={buttonStyle}
onPointerDown={e => {
@@ -815,7 +814,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
TraceMobx();
return (
<div className={`scriptingBox`} onContextMenu={this.specificContextMenu} onPointerUp={!this._function ? this.suggestionPos : undefined}>
- <div className="scriptingBox-outerDiv" onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
+ <div className="scriptingBox-outerDiv" onWheel={e => this.props.isSelected() && e.stopPropagation()}>
{this._paramSuggestion ? (
<div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: 'inline' }}>
{' '}
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
index 2a3dd8d2d..9aab8c5a9 100644
--- a/src/client/views/nodes/TaskCompletedBox.tsx
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -1,13 +1,11 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import "./TaskCompletedBox.scss";
-import { observable, action } from "mobx";
-import { Fade } from "@material-ui/core";
-
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import './TaskCompletedBox.scss';
+import { observable, action } from 'mobx';
+import { Fade } from '@mui/material';
@observer
export class TaskCompletionBox extends React.Component<{}> {
-
@observable public static taskCompleted: boolean = false;
@observable public static popupX: number = 500;
@observable public static popupY: number = 150;
@@ -16,15 +14,20 @@ export class TaskCompletionBox extends React.Component<{}> {
@action
public static toggleTaskCompleted = () => {
TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted;
- }
+ };
render() {
- return <Fade in={TaskCompletionBox.taskCompleted}>
- <div className="taskCompletedBox-fade"
- style={{
- left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
- top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
- }}>{TaskCompletionBox.textDisplayed}</div>
- </Fade>;
+ return (
+ <Fade in={TaskCompletionBox.taskCompleted}>
+ <div
+ className="taskCompletedBox-fade"
+ style={{
+ left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
+ top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
+ }}>
+ {TaskCompletionBox.textDisplayed}
+ </div>
+ </Fade>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f90c19050..ae923ad60 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.mini-viewer {
cursor: grab;
@@ -172,7 +172,11 @@
top: 90%;
transform: translate(-50%, -50%);
width: 80%;
- transition: top 0s, width 0s, opacity 0.3s, visibility 0.3s;
+ transition:
+ top 0s,
+ width 0s,
+ opacity 0.3s,
+ visibility 0.3s;
}
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8bf2f4ce5..df73dffe4 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,8 +1,8 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
+import * as React from 'react';
import { Doc, StrListCast } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -33,7 +33,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
-const path = require('path');
/**
* VideoBox
@@ -52,7 +51,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(VideoBox, fieldKey);
}
-
static _youtubeIframeCounter: number = 0;
static heightPercent = 80; // height of video relative to videoBox when timeline is open
static numThumbnails = 20;
@@ -70,6 +68,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _playRegionTimer: any = null; // timeout for playback
private _controlsFadeTimer: any = null; // timeout for controls fade
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ this._props.setContentView?.(this);
+ }
+
@observable _stackedTimeline: any; // CollectionStackedTimeline ref
@observable static _nativeControls: boolean; // default html controls
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -96,13 +100,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable rawDuration: number = 0;
@computed get youtubeVideoId() {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
}
// returns the path of the audio file
@computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null);
+ const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null);
const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
return field?.url.href ?? vfield?.url.href ?? '';
}
@@ -125,7 +129,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
componentDidMount() {
this.unmounting = false;
- this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+ this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
if (this.youtubeVideoId) {
const youtubeaspect = 400 / 315;
const nativeWidth = Doc.NativeWidth(this.layoutDoc);
@@ -162,7 +166,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (
// need to include range inputs because after dragging time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isSelected()
+ this._props.isSelected()
) {
switch (e.key) {
case 'ArrowLeft':
@@ -267,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.player && this._contentRef && this._contentRef.requestFullscreen();
}
try {
- this._youtubePlayer && this.props.addDocTab(this.rootDoc, OpenWhere.add);
+ this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add);
} catch (e) {
console.log('Video FullScreen Exception:', e);
}
@@ -328,15 +332,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
title: (this.layoutDoc._layout_currentTimecode || 0).toString(),
onClick: FollowLinkScript(),
});
- this.props.addDocument?.(b);
- DocUtils.MakeLink(b, this.rootDoc, { link_relationship: 'video snapshot' });
+ this._props.addDocument?.(b);
+ DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
Networking.PostToServer('/youtubeScreenshot', {
id: this.youtubeVideoId,
timecode: this.layoutDoc._layout_currentTimecode,
}).then(response => {
const resolved = response?.accessPaths?.agnostic?.client;
if (resolved) {
- this.props.removeDocument?.(b);
+ this._props.removeDocument?.(b);
this.createSnapshotLink(resolved);
}
});
@@ -344,7 +348,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
+ const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString().replace(/\./, '_'));
const filename = basename(encodedFilename);
Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
@@ -377,7 +381,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
});
Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
- this.props.addDocument?.(imageSnapshot);
+ this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
@@ -388,9 +392,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
const anchor = addAsAnnotation
- ? CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc
- : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
+ ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? 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 } }, this.Document);
return anchor;
};
@@ -437,7 +441,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
canvas.height = 100;
canvas.width = 100;
canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
- const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
+ const 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));
const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
@@ -492,13 +496,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
subitems.push({ description: 'Full Screen', event: this.FullScreen, icon: 'expand' });
subitems.push({ description: 'Take Snapshot', event: this.Snapshot, icon: 'expand-arrows-alt' });
- this.rootDoc.type === DocumentType.SCREENSHOT &&
+ this.Document.type === DocumentType.SCREENSHOT &&
subitems.push({
description: 'Screen Capture',
event: async () => {
@@ -532,7 +536,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
event: () => {
this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey);
// delete assoicated video data
- this.dataDoc[this.props.fieldKey] = '';
+ this.dataDoc[this._props.fieldKey] = '';
this.dataDoc[this.fieldKey + '_duration'] = '';
// delete assoicated presentation data
this.dataDoc[this.fieldKey + '_presentation'] = '';
@@ -550,7 +554,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders the video and audio
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = Doc.ActiveTool !== InkTool.None || !this.props.isSelected() ? '' : '-interactive';
+ const interactive = Doc.ActiveTool !== InkTool.None || !this._props.isSelected() ? '' : '-interactive';
const classname = 'videoBox-content' + (this._fullScreen ? '-fullScreen' : '') + interactive;
const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
return !field ? (
@@ -575,7 +579,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
key="video"
autoPlay={this._screenCapture}
ref={this.setVideoRef}
- style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.rootDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}}
+ style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.layoutDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}}
onCanPlay={this.videoLoad}
controls={VideoBox._nativeControls}
onPlay={() => this.Play()}
@@ -586,7 +590,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Not supported.
</video>
{!this.audiopath || this.audiopath === field.url.href ? null : (
- <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}>
+ <audio ref={this.setAudioRef} className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
</audio>
@@ -626,7 +630,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
() => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
);
this._disposers.youtubeReactionDisposer = reaction(
- () => Doc.ActiveTool === InkTool.None && this.props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
+ () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.IsDragging && !DocumentView.Interacting,
interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
{ fireImmediately: true }
);
@@ -636,8 +640,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
(YT as any)?.ready(() => {
this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
events: {
- onReady: this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
- onStateChange: this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
+ onReady: this._props.dontRegisterView ? undefined : onYoutubePlayerReady,
+ onStateChange: this._props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
},
});
});
@@ -677,9 +681,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
e,
action(encodeURIComponent => {
this._clicking = false;
- if (this.props.isContentActive()) {
- // const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
- // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+ if (this._props.isContentActive()) {
+ // const local = this._props.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+ // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
this.layoutDoc._layout_timelineHeightPercent = 80;
}
@@ -693,15 +697,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
500
);
},
- this.props.isContentActive(),
- this.props.isContentActive()
+ this._props.isContentActive(),
+ this._props.isContentActive()
);
};
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -710,7 +714,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -861,7 +865,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -882,31 +886,31 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
finishMarquee = () => {
this._marqueeref.current?.onTerminateSelection();
- this.props.select(true);
+ this._props.select(true);
};
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
timelineScreenToLocal = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
.scale(this.scaling())
- .translate(0, (-this.heightPercent / 100) * this.props.PanelHeight());
+ .translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
- timelineHeight = () => (this.props.PanelHeight() * (100 - this.heightPercent)) / 100;
+ timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
playing = () => this._playing;
- scaling = () => this.props.NativeDimScaling?.() || 1;
+ scaling = () => this._props.NativeDimScaling?.() || 1;
- panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
- panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
+ panelWidth = () => (this._props.PanelWidth() * this.heightPercent) / 100;
+ panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this._props.PanelHeight() * this.heightPercent) / 100);
screenToLocalTransform = () => {
- const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
- return this.props
+ const offset = (this._props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
+ return this._props
.ScreenToLocalTransform()
.translate(-offset, 0)
.scale(100 / this.heightPercent);
@@ -918,10 +922,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders video controls
componentUI = (boundsLeft: number, boundsTop: number) => {
- const xf = this.props.ScreenToLocalTransform().inverse();
- const height = this.props.PanelHeight();
+ const xf = this._props.ScreenToLocalTransform().inverse();
+ const height = this._props.PanelHeight();
const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
- const vidWidth = this.props.PanelWidth() / this.scaling();
+ const vidWidth = this._props.PanelWidth() / this.scaling();
const uiHeight = 25;
const uiMargin = 10;
const yBot = xf.transformPoint(0, vidHeight)[1];
@@ -936,7 +940,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="videoBox-ui"
style={{
- transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+ transform: `rotate(${this._props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
left: xPos,
top: yMid,
height: uiHeight,
@@ -957,13 +961,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<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))}
- {...this.props}
+ {...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.audiopath}
thumbnails={this.thumbnails}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* videoStart */}
endTag={'_timecodeToHide' /* videoEnd */}
bringToFront={emptyFunction}
@@ -999,7 +1003,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const cropping = Doc.MakeCopy(region, true);
Doc.GetProto(region).backgroundColor = 'transparent';
Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
+ Doc.GetProto(region).title = 'region:' + this.Document.title;
Doc.GetProto(region).followLinkToggle = true;
region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
this.addDocument(region);
@@ -1007,22 +1011,22 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const anchy = NumCast(cropping.y);
const anchw = NumCast(cropping._width);
const anchh = NumCast(cropping._height);
- const viewScale = NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']) / anchw;
- cropping.title = 'crop: ' + this.rootDoc.title;
- cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
- cropping.y = NumCast(this.rootDoc.y);
- cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
- cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw;
+ cropping.title = 'crop: ' + this.Document.title;
+ cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
+ cropping.y = NumCast(this.Document.y);
+ cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.timecodeToHide = undefined;
cropping.timecodeToShow = undefined;
cropping.onClick = undefined;
const croppingProto = Doc.GetProto(cropping);
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
- croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
croppingProto.type = DocumentType.VID;
croppingProto.layout = VideoBox.LayoutString('data');
- croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
+ croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
croppingProto['data_nativeWidth'] = anchw;
croppingProto['data_nativeHeight'] = anchh;
croppingProto.videoCrop = true;
@@ -1038,12 +1042,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
return cropping;
};
savedAnnotations = () => this._savedAnnotations;
render() {
- const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / this.scaling()}px` : borderRad;
return (
<div
@@ -1053,11 +1057,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
style={{
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this.props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
- }}
- onWheel={e => {
- e.stopPropagation();
- e.preventDefault();
+ overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
}}>
<div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
<div
@@ -1067,19 +1067,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
width: this.panelWidth(),
height: this.panelHeight(),
top: 0,
- left: (this.props.PanelWidth() - this.panelWidth()) / 2,
+ left: (this._props.PanelWidth() - this.panelWidth()) / 2,
}}>
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
isAnyChildContentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocalTransform}
childFilters={this.timelineDocFilter}
@@ -1097,12 +1097,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
- rootDoc={this.rootDoc}
+ Document={this.Document}
scrollTop={0}
annotationLayerScrollTop={0}
scaling={returnOne}
- annotationLayerScaling={this.props.NativeDimScaling}
- docView={this.props.DocumentView!}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ docView={this._props.DocumentView!}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
@@ -1120,7 +1120,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@computed get UIButtons() {
- const bounds = this.props.docViewPath().lastElement().getBounds();
+ const bounds = this._props.docViewPath().lastElement().getBounds();
const width = (bounds?.right || 0) - (bounds?.left || 0);
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
return (
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 511c91da0..a1686adaf 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
.webBox {
height: 100%;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e42fb4a03..57045e2af 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
+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 { Id } from '../../../fields/FieldSymbols';
@@ -33,10 +34,9 @@ import { SidebarAnnos } from '../SidebarAnnos';
import { StyleProp } from '../StyleProvider';
import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { LinkDocPreview } from './LinkDocPreview';
+import { LinkInfo } from './LinkDocPreview';
import { PinProps, PresBox } from './trails';
import './WebBox.scss';
-import React = require('react');
const { CreateImage } = require('./WebBoxRenderer');
const _global = (window /* browser */ || global) /* node */ as any;
const htmlToText = require('html-to-text');
@@ -67,7 +67,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _searching: boolean = false;
@observable private _showSidebar = false;
@observable private _webPageHasBeenRendered = false;
- @observable private _marqueeing: number[] | undefined;
+ @observable private _marqueeing: number[] | undefined = undefined;
get marqueeing() {
return this._marqueeing;
}
@@ -95,12 +95,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this.allAnnotations.filter(a => a.text_inlineAnnotations);
}
@computed get webField() {
- return Cast(this.rootDoc[this.props.fieldKey], WebField)?.url;
+ return Cast(this.Document[this._props.fieldKey], WebField)?.url;
}
constructor(props: any) {
super(props);
- runInAction(() => (this._webUrl = this._url)); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
+ makeObservable(this);
+ this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
}
@action
@@ -139,13 +140,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this._iframe) return;
const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth();
+ const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
if (!htmlString) {
htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
}
this.layoutDoc.thumb = undefined;
- this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates.
+ 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')) {
@@ -155,7 +156,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
setTimeout(
action(() => {
- this.rootDoc.thumbLockout = false;
+ this.Document.thumbLockout = false;
this.layoutDoc.thumb = new ImageField(returnedfilename);
this.layoutDoc.thumbScrollTop = scrollTop;
this.layoutDoc.thumbNativeWidth = nativeWidth;
@@ -170,8 +171,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
};
- async componentDidMount() {
- this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
+ componentDidMount() {
+ this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
runInAction(() => {
this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
@@ -185,11 +186,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
});
this._disposers.urlchange = reaction(
- () => WebCast(this.rootDoc.data),
+ () => WebCast(this.dataDoc.data),
url => this.submitURL(false, false)
);
this._disposers.titling = reaction(
- () => StrCast(this.rootDoc.title),
+ () => StrCast(this.Document.title),
url => {
url.startsWith('www') && this.setData('http://' + url);
url.startsWith('http') && this.setData(url);
@@ -200,8 +201,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
() => this.layoutDoc._layout_autoHeight,
layout_autoHeight => {
if (layout_autoHeight) {
- this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
- this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
+ this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
+ this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
}
);
@@ -218,10 +219,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
} // else it's an HTMLfield
} else if (this.webField && !this.dataDoc.text) {
- const result = await WebRequest.get(Utils.CorsProxy(this.webField.href));
- if (result) {
- this.dataDoc.text = htmlToText.fromString(result.content);
- }
+ WebRequest.get(Utils.CorsProxy(this.webField.href)) //
+ .then(result => result && (this.dataDoc.text = htmlToText.convert(result.content)));
}
this._disposers.scrollReaction = reaction(
@@ -236,7 +235,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
{ fireImmediately: true }
);
}
- @action componentWillUnmount() {
+ componentWillUnmount() {
this._iframetimeout && clearTimeout(this._iframetimeout);
this._iframetimeout = undefined;
Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -251,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
if (this._mainCont.current && selRange) {
- if (this.rootDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
+ if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
@@ -259,7 +258,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (rect && rect.width !== this._mainCont.current.clientWidth) {
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
- const scale = this._url ? 1 : this.props.ScreenToLocalTransform().Scale;
+ const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale;
// transforms the positions from screen onto the pdf div
annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
@@ -281,8 +280,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
focus = (anchor: Doc, options: DocFocusOptions) => {
- if (anchor !== this.rootDoc && this._outerRef.current) {
- const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+ 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));
if (scrollTo !== undefined) {
if (this._initialScroll === undefined) {
@@ -298,8 +297,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
getView = (doc: Doc) => {
- if (Doc.AreProtosEqual(doc, this.rootDoc)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.()));
- if (this.rootDoc.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify();
+ if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
+ if (this.Document.layout_fieldKey === 'layout_icon') this._props.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);
@@ -307,11 +306,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
- if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+ if (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(doc, where);
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
let ele: Opt<HTMLDivElement> = undefined;
@@ -326,11 +325,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const anchor =
visibleAnchor ??
Docs.Create.ConfigDocument({
- title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
+ title: StrCast(this.Document.title + ' ' + this.layoutDoc._layout_scrollTop),
y: NumCast(this.layoutDoc._layout_scrollTop),
- annotationOn: this.rootDoc,
+ annotationOn: this.Document,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.Document);
anchor.text = ele?.textContent ?? '';
anchor.text_html = ele?.innerHTML ?? this._selectionText;
addAsAnnotation && this.addDocumentWrapper(anchor);
@@ -344,10 +343,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
iframeUp = (e: PointerEvent) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
this._textAnnotationCreator = undefined;
- this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+ this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
- const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
+ const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
this._selectionText = sel.toString();
@@ -355,7 +354,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
// Changing which document to add the annotation to (the currently selected WebBox)
- GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+ GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
}
@@ -369,7 +368,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
// bcz: NEED TO unrotate e.clientX and e.clientY
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
- this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
+ this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.Document);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
@@ -393,23 +392,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
(!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
// Changing which document to add the annotation to (the currently selected WebBox)
- GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+ GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
};
@action
iframeDown = (e: PointerEvent) => {
- this.props.select(false);
+ this._props.select(false);
const locpt = {
- x: (e.clientX / NumCast(this.rootDoc.nativeWidth)) * this.props.PanelWidth(),
- y: ((e.clientY - NumCast(this.rootDoc.layout_scrollTop))/ NumCast(this.rootDoc.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
- const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
- const scrcent = this.props
+ x: (e.clientX / NumCast(this.Document.nativeWidth)) * this._props.PanelWidth(),
+ y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this._props.PanelHeight() }; // prettier-ignore
+ const scrclick = this._props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+ const scrcent = this._props
.DocumentView?.()
- .props.ScreenToLocalTransform()
+ ._props.ScreenToLocalTransform()
.inverse()
- .transformPoint(NumCast(this.rootDoc.width) / 2, NumCast(this.rootDoc.height) / 2)!;
- const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate);
+ .transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2)!;
+ const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this._props.ScreenToLocalTransform().Rotate);
const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]];
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -488,15 +487,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const initHeights = () => {
this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
if (this._scrollHeight) {
- this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
- this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+ this.Document.nativeHeight = Math.min(NumCast(this.Document.nativeHeight), this._scrollHeight);
+ this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth));
}
};
const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
if (swidth) {
- const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth);
- this.rootDoc.nativeWidth = swidth;
- this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize;
+ const aspectResize = swidth / NumCast(this.Document.nativeWidth);
+ this.Document.nativeWidth = swidth;
+ this.Document.nativeHeight = NumCast(this.Document.nativeHeight) * aspectResize;
this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
}
initHeights();
@@ -564,7 +563,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
clearStyleSheetRules(WebBox.webStyleSheet);
this._scrollTimer = undefined;
const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
- if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
+ if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
this.layoutDoc.thumb = undefined;
this.layoutDoc.thumbScrollTop = undefined;
this.layoutDoc.thumbNativeWidth = undefined;
@@ -586,8 +585,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
forward = (checkAvailable?: boolean) => {
- const future = Cast(this.rootDoc[this.fieldKey + '_future'], listSpec('string'), []);
- const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'), []);
+ const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
if (checkAvailable) return future.length;
runInAction(() => {
if (future.length) {
@@ -608,8 +607,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
back = (checkAvailable?: boolean) => {
- const future = Cast(this.rootDoc[this.fieldKey + '_future'], listSpec('string'));
- const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'));
+ const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
if (checkAvailable) return history.length;
runInAction(() => {
if (history.length) {
@@ -677,7 +676,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (Field.toString(data) === this._url) return false;
this._scrollHeight = 0;
const oldUrl = this._url;
- const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
const weburl = new WebField(Field.toString(data));
this.dataDoc[this.fieldKey + '_future'] = new List<string>([]);
this.dataDoc[this.fieldKey + '_history'] = new List<string>([...(history || []), oldUrl]);
@@ -709,7 +708,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
funcs.push({
description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
event: () => {
- const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
+ const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this._props.NativeDimScaling?.() || 1);
this.layoutDoc.layout_reflowHorizontal = !nw;
if (nw) {
Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true);
@@ -734,7 +733,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (sel?.empty) sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this.marqueeing = [e.clientX, e.clientY];
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -760,7 +759,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
if (sel?.empty) sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
- this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.rootDoc);
+ this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
if (x !== undefined && y !== undefined) {
ContextMenu.Instance.closeMenu();
ContextMenu.Instance.setIgnoreEvents(false);
@@ -770,7 +769,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+ this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
});
}
}
@@ -779,7 +778,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable lighttext = false;
@computed get urlContent() {
- if (this.props.ScreenToLocalTransform().Scale > 25) return <div></div>;
+ if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>;
setTimeout(
action(() => {
if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -788,7 +787,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._webPageHasBeenRendered = true;
})
);
- const field = this.rootDoc[this.props.fieldKey];
+ const field = this.dataDoc[this._props.fieldKey];
if (field instanceof HtmlField) {
return (
<span
@@ -845,14 +844,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e,
action((e, down, delta) => {
this._draggingSidebar = true;
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight']);
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+ const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth;
if (ratio >= 1) {
this.layoutDoc.nativeWidth = nativeWidth * ratio;
this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
@@ -891,7 +890,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
toggleSidebar = action((preview: boolean = false) => {
var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
if (!nativeWidth) {
- const defaultNativeWidth = NumCast(this.rootDoc.nativeWidth, this.rootDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
+ 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);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth);
nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
@@ -916,7 +915,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this.props.isContentActive(true)) {
+ if (this._props.isContentActive(true)) {
e.stopPropagation();
}
};
@@ -924,14 +923,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this.SidebarShown) return 0;
if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
- return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
+ return WebBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
};
_innerCollectionView: CollectionFreeFormView | undefined;
zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView);
@computed get content() {
- const interactive = this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
+ const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
return (
<div
className={'webBox-cont' + (interactive ? '-interactive' : '')}
@@ -959,7 +958,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
{this.inlineTextAnnotations
.sort((a, b) => NumCast(a.y) - NumCast(b.y))
.map(anno => (
- <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
@@ -969,13 +968,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
renderAnnotations = (childFilters: () => string[]) => (
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
setContentView={this.setInnerContent}
NativeWidth={returnZero}
NativeHeight={returnZero}
originTopLeft={false}
isAnnotationOverlayScrollable={true}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
@@ -1004,11 +1003,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get renderTransparentAnnotations() {
return this.renderAnnotations(this.transparentFilter);
}
- childPointerEvents = () => (this.props.isContentActive() ? 'all' : undefined);
+ childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
- const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
+ const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return (
<div
className="webBox-outerContent"
@@ -1021,9 +1020,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onWheel={this.onZoomWheel}
onScroll={e => 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 }}>
+ <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
{this.content}
- <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
+ <div style={{ display: SnappingManager.CanEmbed ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
{this.renderOpaqueAnnotations}
{this.annotationLayer}
</div>
@@ -1033,7 +1032,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get searchUI() {
return (
- <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+ <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="webBox-overlayButton" title={'search'} />
<input
@@ -1067,36 +1066,36 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
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.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+ panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
+ panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+ scrollXf = () => this._props.ScreenToLocalTransform().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.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])];
+ transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc)) return 'none';
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
pointerEvents = () =>
- !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
+ !this._draggingSidebar && this._props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
? 'all' //
: 'none';
- annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
+ annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
- const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
+ const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return (
<div
className="webBox"
ref={this._mainCont}
style={{
pointerEvents: this.pointerEvents(), //
- position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
+ position: SnappingManager.IsDragging ? 'absolute' : undefined,
}}>
- <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
+ <div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }} />
<div
className="webBox-container"
style={{
@@ -1111,13 +1110,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
<MarqueeAnnotator
ref={this._marqueeref}
- rootDoc={this.rootDoc}
+ Document={this.Document}
anchorMenuClick={this.anchorMenuClick}
scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
annotationLayerScrollTop={0}
- scaling={this.props.NativeDimScaling}
+ scaling={this._props.NativeDimScaling}
addDocument={this.addDocumentWrapper}
- docView={this.props.DocumentView!}
+ docView={this._props.DocumentView!}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotationsCreator}
selectionText={this.selectionText}
@@ -1135,25 +1134,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}}
onPointerDown={e => this.sidebarBtnDown(e, false)}
/>
- <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>
+ <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
fieldKey={this.fieldKey + '_' + this._urlHash}
- rootDoc={this.rootDoc}
+ Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
setHeight={emptyFunction}
- nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this._props.NativeDimScaling?.() || 1)}
showSidebar={this.SidebarShown}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
/>
</div>
- {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle}
- {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI}
+ {!this._props.isContentActive() || SnappingManager.IsDragging ? null : this.sidebarHandle}
+ {!this._props.isContentActive() || SnappingManager.IsDragging ? null : this.searchUI}
</div>
);
}
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/nodes/audio/AudioWaveform.scss
index 6cbd1759a..6cbd1759a 100644
--- a/src/client/views/AudioWaveform.scss
+++ b/src/client/views/nodes/audio/AudioWaveform.scss
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
new file mode 100644
index 000000000..01392c4a5
--- /dev/null
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -0,0 +1,122 @@
+import axios from 'axios';
+import { computed, IReactionDisposer, makeObservable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, NumListCast } from '../../../../fields/Doc';
+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 './AudioWaveform.scss';
+import { WaveCanvas } from './WaveCanvas';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+
+/**
+ * AudioWaveform
+ *
+ * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
+ * Uses react-audio-waveform package.
+ * Bins the audio data into audioBuckets which are passed to package to render the lines.
+ * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
+ */
+
+export interface AudioWaveformProps {
+ duration: number; // length of media clip
+ rawDuration: number; // length of underlying media data
+ mediaPath: string;
+ layoutDoc: Doc;
+ clipStart: number;
+ clipEnd: number;
+ zoomFactor: number;
+ PanelHeight: number;
+ PanelWidth: number;
+ fieldKey: string;
+ progress?: number;
+}
+
+@observer
+export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps> {
+ public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
+ _disposer: IReactionDisposer | undefined;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ get waveHeight() {
+ return Math.max(50, this._props.PanelHeight);
+ }
+
+ get clipStart() {
+ return this._props.clipStart;
+ }
+ get clipEnd() {
+ return this._props.clipEnd;
+ }
+ get zoomFactor() {
+ return this._props.zoomFactor;
+ }
+
+ @computed get audioBuckets() {
+ 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;
+
+ componentWillUnmount() {
+ this._disposer?.();
+ }
+
+ componentDidMount() {
+ 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') {
+ // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
+ const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
+ this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
+ setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
+ }
+ },
+ { fireImmediately: true }
+ );
+ }
+
+ // decodes the audio file into peaks for generating the waveform
+ createWaveformBuckets = (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
+ axios({ url: this._props.mediaPath, responseType: 'arraybuffer' }).then(response =>
+ new window.AudioContext().decodeAudioData(response.data, buffer => {
+ const rawDecodedAudioData = buffer.getChannelData(0);
+ const startInd = clipStart / this._props.rawDuration;
+ const endInd = clipEnd / this._props.rawDuration;
+ const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
+
+ const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
+ const brange = Array.from(Array(bucketDataSize));
+ const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
+ this._props.layoutDoc[fieldKey] = new List<number>(bucketList);
+ })
+ );
+ };
+
+ render() {
+ return (
+ <div className="audioWaveform">
+ <WaveCanvas
+ color={Colors.LIGHT_GRAY}
+ progressColor={Colors.MEDIUM_BLUE_ALT}
+ progress={this._props.progress ?? 1}
+ barWidth={200 / this.audioBuckets.length}
+ //gradientColors={this.props.gradientColors}
+ peaks={this.audioBuckets}
+ width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
+ height={this.waveHeight * window.devicePixelRatio}
+ pixelRatio={window.devicePixelRatio}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
new file mode 100644
index 000000000..d3f5669a2
--- /dev/null
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+
+interface WaveCanvasProps {
+ barWidth: number;
+ color: string;
+ progress: number;
+ progressColor: string;
+ gradientColors?: { stopPosition: number; color: string }[]; // stopPosition between 0 and 1
+ peaks: number[];
+ width: number;
+ height: number;
+ pixelRatio: number;
+}
+
+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);
+
+ drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+ // Bar wave draws the bottom only as a reflection of the top,
+ // so we don't need negative values
+ const posPeaks = peaks.some(val => val < 0) ? this.posPeaks(peaks, peaks[0] < 0 ? 1 : 0) : peaks;
+
+ // A half-pixel offset makes lines crisp
+ const $ = 0.5 / this.props.pixelRatio;
+ const bar = this.props.barWidth * this.props.pixelRatio;
+ const gap = Math.max(this.props.pixelRatio, 2);
+
+ const max = Math.max(...posPeaks);
+ const scale = posPeaks.length / width;
+
+ for (let i = 0; i < width; i += bar + gap) {
+ if (i > width * this.props.progress) waveCanvasCtx.fillStyle = this.props.color;
+
+ const h = Math.round((posPeaks[Math.floor(i * scale)] / max) * halfH) || 1;
+
+ waveCanvasCtx.fillRect(i + $, halfH - h, bar + $, h * 2);
+ }
+ };
+
+ addNegPeaks = (peaks: number[]) =>
+ peaks.reduce((reflectedPeaks, peak) => reflectedPeaks.push(peak, -peak) ? reflectedPeaks:[],
+ [] as number[]); // prettier-ignore
+
+ drawWaves = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+ const allPeaks = peaks.some(val => val < 0) ? peaks : this.addNegPeaks(peaks); // add negative peaks to arrays without negative peaks
+
+ // A half-pixel offset makes lines crisp
+ const $ = 0.5 / this.props.pixelRatio;
+ const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
+
+ const scale = width / length;
+ const absmax = Math.max(...allPeaks.map(peak => Math.abs(peak)));
+
+ waveCanvasCtx.beginPath();
+ waveCanvasCtx.moveTo($, halfH);
+
+ for (var i = 0; i < length; i++) {
+ var 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);
+ waveCanvasCtx.lineTo(i * scale + $, halfH - h);
+ }
+
+ waveCanvasCtx.fill();
+
+ // Always draw a median line
+ waveCanvasCtx.fillRect(0, halfH - $, width, $);
+ };
+
+ updateSize = (width: number, height: number, peaks: number[], waveCanvasCtx: CanvasRenderingContext2D) => {
+ const displayWidth = Math.round(width / this.props.pixelRatio);
+ const displayHeight = Math.round(height / this.props.pixelRatio);
+ waveCanvasCtx.canvas.width = width;
+ waveCanvasCtx.canvas.height = height;
+ waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
+ waveCanvasCtx.canvas.style.height = `${displayHeight}px`;
+
+ waveCanvasCtx.clearRect(0, 0, width, height);
+
+ const gradient = this.props.gradientColors && waveCanvasCtx.createLinearGradient(0, 0, width, 0);
+ gradient && this.props.gradientColors?.forEach(color => gradient.addColorStop(color.stopPosition, color.color));
+ waveCanvasCtx.fillStyle = gradient ?? this.props.progressColor;
+
+ const waveDrawer = this.props.barWidth ? this.drawBars : this.drawWaves;
+ waveDrawer(waveCanvasCtx, width, height / 2, peaks);
+ };
+
+ render() {
+ return this.props.peaks ? (
+ <div style={{ position: 'relative', width: '100%', height: '100%', cursor: 'pointer' }}>
+ <canvas ref={instance => (ctx => ctx && this.updateSize(this.props.width, this.props.height, this.props.peaks, ctx))(instance?.getContext('2d'))} />
+ </div>
+ ) : null;
+ }
+}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
new file mode 100644
index 000000000..989feb774
--- /dev/null
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -0,0 +1,134 @@
+import * as React from 'react';
+import { observer } from "mobx-react";
+import { Doc } from "../../../../fields/Doc";
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { StrCast } from '../../../../fields/Types';
+import { makeObservable } from 'mobx';
+import { dateRangeStrToDates } from '../../../../Utils';
+import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'
+import dayGridPlugin from '@fullcalendar/daygrid'
+import multiMonthPlugin from '@fullcalendar/multimonth'
+import { faListNumeric } from '@fortawesome/free-solid-svg-icons';
+
+type CalendarView = 'month' | 'multi-month' | 'week';
+
+@observer
+export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
+ public static LayoutString(fieldKey: string = 'calendar') {
+ return FieldView.LayoutString(CalendarBox, fieldKey);
+ }
+
+ componentDidMount(): void {
+
+ }
+
+ componentWillUnmount(): void {
+
+ }
+
+ _calendarRef = React.createRef<HTMLElement>()
+
+ get dateRangeStr (){
+ return StrCast(this.Document.date_range);
+ }
+
+ // Choose a calendar view based on the date range
+ get calendarViewType (): CalendarView {
+ const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
+
+ if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
+
+ if (Math.abs(fromDate.getDay() - toDate.getDay()) > 7) return 'month';
+ return 'week';
+ }
+
+ get calendarStartDate () {
+ return this.dateRangeStr.split("|")[0];
+ }
+
+ get calendarToDate () {
+ return this.dateRangeStr.split("|")[1];
+ }
+
+ get childDocs (): Doc[] {
+ return this.childDocs; // get all sub docs for a calendar
+ }
+
+ docBackgroundColor (type: string): string {
+ // TODO: Return a different color based on the event type
+ return 'blue';
+ }
+
+ get calendarEvents (): EventSourceInput | undefined {
+ if (this.childDocs.length === 0) return undefined;
+ return this.childDocs.map((doc, idx) => {
+ const docTitle = StrCast(doc.title);
+ const docDateRange = StrCast(doc.date_range);
+ const [startDate, endDate] = dateRangeStrToDates(docDateRange);
+ const docType = doc.type;
+ const docDescription = doc.description ? StrCast(doc.description): "";
+
+ return {
+ title: docTitle,
+ start: startDate,
+ end: endDate,
+ allDay: false,
+ classNames:[StrCast(docType)], // will determine the style
+ editable: false, // subject to change in the future
+ backgroundColor: this.docBackgroundColor(StrCast(doc.type)),
+ color: 'white',
+ extendedProps: {
+ description: docDescription
+ },
+
+ }
+
+ })
+ }
+
+ handleEventClick = (arg: EventClickArg) => {
+ // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
+ }
+
+ calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!;
+
+ // https://fullcalendar.io
+ get calendar() {
+ return new Calendar(this.calendarEl,
+ {
+ plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
+ headerToolbar: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
+ },
+ initialDate: this.calendarStartDate,
+ navLinks: true,
+ editable: false,
+ displayEventTime: false,
+ displayEventEnd: false,
+ events: this.calendarEvents,
+ eventClick: this.handleEventClick
+ }
+ )
+
+ }
+
+
+ constructor(props: any){
+ super(props);
+ makeObservable(this);
+ }
+
+ render(){
+ return (
+ <div className='calendar-box-conatiner'>
+ <div id='calendar-box-v1'>
+
+ </div>
+ </div>
+ );
+
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index aa269d8d6..b7d2a24c2 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -2,7 +2,7 @@ import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
-import React = require('react');
+import * as React from 'react';
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
@@ -54,7 +54,7 @@ interface IDashDocCommentViewInternal {
}
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
- constructor(props: IDashDocCommentViewInternal) {
+ constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this);
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index e8b9a98b7..6332b200d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -11,7 +11,7 @@ import { Docs, DocUtils } from '../../../documents/Documents';
import { Transform } from '../../../util/Transform';
import { DocFocusOptions, DocumentView } from '../DocumentView';
import { FormattedTextBox } from './FormattedTextBox';
-import React = require('react');
+import * as React from 'react';
export class DashDocView {
dom: HTMLSpanElement; // container for label and value
@@ -82,7 +82,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
_spanRef = React.createRef<HTMLDivElement>();
_disposers: { [name: string]: IReactionDisposer } = {};
_textBox: FormattedTextBox;
- @observable _dashDoc: Doc | undefined;
+ @observable _dashDoc: Doc | undefined = undefined;
@observable _finalLayout: any;
@observable _width: number = 0;
@observable _height: number = 0;
@@ -202,7 +202,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
<DocumentView
Document={this._finalLayout}
addDocument={returnFalse}
- rootSelected={returnFalse} //{this._textBox.props.isSelected}
removeDocument={this.removeDoc}
isDocumentActive={returnFalse}
isContentActive={this.isContentActive}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index ad315acc8..3426ba1a7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
.dashFieldView {
position: relative;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 0a64c8062..555b752f0 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,23 +1,24 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Cast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { Transform } from '../../../util/Transform';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import React = require('react');
-import { Transform } from '../../../util/Transform';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -25,7 +26,7 @@ export class DashFieldView {
node: any;
tbox: FormattedTextBox;
- unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ unclickable = () => !this.tbox._props.isSelected() && 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) {
this.node = node;
this.tbox = tbox;
@@ -70,10 +71,10 @@ export class DashFieldView {
} catch {}
});
}
- @action deselectNode() {
+ deselectNode() {
this.dom.classList.remove('ProseMirror-selectednode');
}
- @action selectNode() {
+ selectNode() {
this.dom.classList.add('ProseMirror-selectednode');
}
}
@@ -92,25 +93,27 @@ interface IDashFieldViewInternal {
}
@observer
-export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
+export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldViewInternal> {
_reactionDisposer: IReactionDisposer | undefined;
_textBoxDoc: Doc;
_fieldKey: string;
_fieldStringRef = React.createRef<HTMLSpanElement>();
- @observable _dashDoc: Doc | undefined;
+ @observable _dashDoc: Doc | undefined = undefined;
@observable _expanded = false;
constructor(props: IDashFieldViewInternal) {
super(props);
- this._fieldKey = this.props.fieldKey;
- this._textBoxDoc = this.props.tbox.props.Document;
+ makeObservable(this);
+ this._fieldKey = this._props.fieldKey;
+ this._textBoxDoc = this._props.tbox.Document;
- if (this.props.docId) {
- DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ if (this._props.docId) {
+ DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
- this._dashDoc = this.props.tbox.rootDoc;
+ this._dashDoc = this._props.tbox.Document;
}
}
+
componentWillUnmount() {
this._reactionDisposer?.();
}
@@ -119,18 +122,18 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// 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.props.hideKey ? this.props.tbox.props.PanelWidth() - 20 : undefined }}>
+ <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
<SchemaTableCell
Document={this._dashDoc}
col={0}
deselectCell={emptyFunction}
selectCell={emptyFunction}
- maxWidth={this.props.hideKey ? undefined : this.return100}
- columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero}
+ maxWidth={this._props.hideKey ? undefined : this.return100}
+ columnWidth={this._props.hideKey ? () => this._props.tbox._props.PanelWidth() - 20 : returnZero}
selectedCell={() => [this._dashDoc!, 0]}
fieldKey={this._fieldKey}
rowHeight={returnZero}
- isRowActive={() => this._expanded && this.props.editable}
+ isRowActive={() => this._expanded && this._props.editable}
padding={0}
getFinfo={emptyFunction}
setColumnValues={returnFalse}
@@ -145,9 +148,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
createPivotForField = (e: React.MouseEvent) => {
- let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
+ let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
if (container) {
- const embedding = Doc.MakeEmbedding(container.rootDoc);
+ const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders';
let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField));
@@ -157,7 +160,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
- this.props.tbox.props.addDocTab(embedding, OpenWhere.addRight);
+ this._props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
}
};
@@ -175,17 +178,17 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
<div
className="dashFieldView"
style={{
- width: this.props.width,
- height: this.props.height,
- pointerEvents: this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+ width: this._props.width,
+ height: this._props.height,
+ pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
- {this.props.hideKey ? null : (
+ {this._props.hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
{this._fieldKey}
</span>
)}
- {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
</div>
);
}
@@ -198,7 +201,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
super(props);
DashFieldViewMenu.Instance = this;
}
- @action
+
showFields = (e: React.MouseEvent) => {
DashFieldViewMenu.createFieldView(e);
DashFieldViewMenu.Instance.fadeOut(true);
diff --git a/src/client/views/nodes/formattedText/EquationEditor.scss b/src/client/views/nodes/formattedText/EquationEditor.scss
new file mode 100644
index 000000000..b0c17e56e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/EquationEditor.scss
@@ -0,0 +1,468 @@
+// using this import, we get runtime errors when trying to load the specified font-faces
+// so we copy the .css and remove the @font-face imports
+
+// @import 'mathquill/build/mathquill.css'
+/*
+ * MathQuill v0.10.1 http://mathquill.com
+ * by Han, Jeanine, and Mary maintainers@mathquill.com
+ *
+ * This Source Code Form is subject to the terms of the
+ * Mozilla Public License, v. 2.0. If a copy of the MPL
+ * was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ */
+// @font-face {
+// font-family: Symbola;
+// src: url(font/Symbola.eot);
+// src:
+// local('Symbola Regular'),
+// local('Symbola'),
+// url(font/Symbola.woff2) format('woff2'),
+// url(font/Symbola.woff) format('woff'),
+// url(font/Symbola.ttf) format('truetype'),
+// url(font/Symbola.otf) format('opentype'),
+// url(font/Symbola.svg#Symbola) format('svg');
+// }
+.mq-editable-field {
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-editable-field .mq-cursor {
+ border-left: 1px solid black;
+ margin-left: -1px;
+ position: relative;
+ z-index: 1;
+ padding: 0;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-editable-field .mq-cursor.mq-blink {
+ visibility: hidden;
+}
+.mq-editable-field,
+.mq-math-mode .mq-editable-field {
+ border: 1px solid gray;
+}
+.mq-editable-field.mq-focused,
+.mq-math-mode .mq-editable-field.mq-focused {
+ -webkit-box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ -moz-box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ border-color: #709ac0;
+ border-radius: 1px;
+}
+.mq-math-mode .mq-editable-field {
+ margin: 1px;
+}
+.mq-editable-field .mq-latex-command-input {
+ color: inherit;
+ font-family: 'Courier New', monospace;
+ border: 1px solid gray;
+ padding-right: 1px;
+ margin-right: 1px;
+ margin-left: 2px;
+}
+.mq-editable-field .mq-latex-command-input.mq-empty {
+ background: transparent;
+}
+.mq-editable-field .mq-latex-command-input.mq-hasCursor {
+ border-color: ActiveBorder;
+}
+.mq-editable-field.mq-empty:after,
+.mq-editable-field.mq-text-mode:after,
+.mq-math-mode .mq-empty:after {
+ visibility: hidden;
+ content: 'c';
+}
+.mq-editable-field .mq-cursor:only-child:after,
+.mq-editable-field .mq-textarea + .mq-cursor:last-child:after {
+ visibility: hidden;
+ content: 'c';
+}
+.mq-editable-field .mq-text-mode .mq-cursor:only-child:after {
+ content: '';
+}
+.mq-editable-field.mq-text-mode {
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+.mq-root-block,
+.mq-math-mode .mq-root-block {
+ display: -moz-inline-box;
+ display: inline-block;
+ width: 100%;
+ padding: 2px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ white-space: nowrap;
+ overflow: hidden;
+ vertical-align: middle;
+}
+.mq-math-mode {
+ font-variant: normal;
+ font-weight: normal;
+ font-style: normal;
+ font-size: 115%;
+ line-height: 1;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-non-leaf,
+.mq-math-mode .mq-scaled {
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode var,
+.mq-math-mode .mq-text-mode,
+.mq-math-mode .mq-nonSymbola {
+ font-family: 'Times New Roman', Symbola, serif;
+ line-height: 0.9;
+}
+.mq-math-mode * {
+ font-size: inherit;
+ line-height: inherit;
+ margin: 0;
+ padding: 0;
+ border-color: black;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ box-sizing: border-box;
+}
+.mq-math-mode .mq-empty {
+ background: #ccc;
+}
+.mq-math-mode .mq-empty.mq-root-block {
+ background: transparent;
+}
+.mq-math-mode.mq-empty {
+ background: transparent;
+}
+.mq-math-mode .mq-text-mode {
+ display: inline-block;
+}
+.mq-math-mode .mq-text-mode.mq-hasCursor {
+ box-shadow: inset darkgray 0 0.1em 0.2em;
+ padding: 0 0.1em;
+ margin: 0 -0.1em;
+ min-width: 1ex;
+}
+.mq-math-mode .mq-font {
+ font:
+ 1em 'Times New Roman',
+ Symbola,
+ serif;
+}
+.mq-math-mode .mq-font * {
+ font-family: inherit;
+ font-style: inherit;
+}
+.mq-math-mode b,
+.mq-math-mode b.mq-font {
+ font-weight: bolder;
+}
+.mq-math-mode var,
+.mq-math-mode i,
+.mq-math-mode i.mq-font {
+ font-style: italic;
+}
+.mq-math-mode var.mq-f {
+ margin-right: 0.2em;
+ margin-left: 0.1em;
+}
+.mq-math-mode .mq-roman var.mq-f {
+ margin: 0;
+}
+.mq-math-mode big {
+ font-size: 200%;
+}
+.mq-math-mode .mq-int > big {
+ display: inline-block;
+ -webkit-transform: scaleX(0.7);
+ -moz-transform: scaleX(0.7);
+ -ms-transform: scaleX(0.7);
+ -o-transform: scaleX(0.7);
+ transform: scaleX(0.7);
+ vertical-align: -0.16em;
+}
+.mq-math-mode .mq-int > .mq-supsub {
+ font-size: 80%;
+ vertical-align: -1.1em;
+ padding-right: 0.2em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sup > .mq-sup-inner {
+ vertical-align: 1.3em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sub {
+ margin-left: -0.35em;
+}
+.mq-math-mode .mq-roman {
+ font-style: normal;
+}
+.mq-math-mode .mq-sans-serif {
+ font-family: sans-serif, Symbola, serif;
+}
+.mq-math-mode .mq-monospace {
+ font-family: monospace, Symbola, serif;
+}
+.mq-math-mode .mq-overline {
+ border-top: 1px solid black;
+ margin-top: 1px;
+}
+.mq-math-mode .mq-underline {
+ border-bottom: 1px solid black;
+ margin-bottom: 1px;
+}
+.mq-math-mode .mq-binary-operator {
+ padding: 0 0.2em;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-supsub {
+ text-align: left;
+ font-size: 90%;
+ vertical-align: -0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only {
+ vertical-align: 0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only .mq-sup {
+ display: inline-block;
+ vertical-align: text-bottom;
+}
+.mq-math-mode .mq-supsub .mq-sup {
+ display: block;
+}
+.mq-math-mode .mq-supsub .mq-sub {
+ display: block;
+ float: left;
+}
+.mq-math-mode .mq-supsub .mq-binary-operator {
+ padding: 0 0.1em;
+}
+.mq-math-mode .mq-supsub .mq-fraction {
+ font-size: 70%;
+}
+.mq-math-mode sup.mq-nthroot {
+ font-size: 80%;
+ vertical-align: 0.8em;
+ margin-right: -0.6em;
+ margin-left: 0.2em;
+ min-width: 0.5em;
+}
+.mq-math-mode .mq-paren {
+ padding: 0 0.1em;
+ vertical-align: top;
+ -webkit-transform-origin: center 0.06em;
+ -moz-transform-origin: center 0.06em;
+ -ms-transform-origin: center 0.06em;
+ -o-transform-origin: center 0.06em;
+ transform-origin: center 0.06em;
+}
+.mq-math-mode .mq-paren.mq-ghost {
+ color: silver;
+}
+.mq-math-mode .mq-paren + span {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+.mq-math-mode .mq-array {
+ vertical-align: middle;
+ text-align: center;
+}
+.mq-math-mode .mq-array > span {
+ display: block;
+}
+.mq-math-mode .mq-operator-name {
+ font-family: Symbola, 'Times New Roman', serif;
+ line-height: 0.9;
+ font-style: normal;
+}
+.mq-math-mode var.mq-operator-name.mq-first {
+ padding-left: 0.2em;
+}
+.mq-math-mode var.mq-operator-name.mq-last,
+.mq-math-mode .mq-supsub.mq-after-operator-name {
+ padding-right: 0.2em;
+}
+.mq-math-mode .mq-fraction {
+ font-size: 90%;
+ text-align: center;
+ vertical-align: -0.4em;
+ padding: 0 0.2em;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link {
+ display: -moz-groupbox;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link,
+.mq-math-mode x:default {
+ display: inline-block;
+}
+.mq-math-mode .mq-numerator,
+.mq-math-mode .mq-denominator {
+ display: block;
+}
+.mq-math-mode .mq-numerator {
+ padding: 0 0.1em;
+}
+.mq-math-mode .mq-denominator {
+ border-top: 1px solid;
+ float: right;
+ width: 100%;
+ padding: 0.1em;
+}
+.mq-math-mode .mq-sqrt-prefix {
+ padding-top: 0;
+ position: relative;
+ top: 0.1em;
+ vertical-align: top;
+ -webkit-transform-origin: top;
+ -moz-transform-origin: top;
+ -ms-transform-origin: top;
+ -o-transform-origin: top;
+ transform-origin: top;
+}
+.mq-math-mode .mq-sqrt-stem {
+ border-top: 1px solid;
+ margin-top: 1px;
+ padding-left: 0.15em;
+ padding-right: 0.2em;
+ margin-right: 0.1em;
+ padding-top: 1px;
+}
+.mq-math-mode .mq-vector-prefix {
+ display: block;
+ text-align: center;
+ line-height: 0.25em;
+ margin-bottom: -0.1em;
+ font-size: 0.75em;
+}
+.mq-math-mode .mq-vector-stem {
+ display: block;
+}
+.mq-math-mode .mq-large-operator {
+ vertical-align: -0.2em;
+ padding: 0.2em;
+ text-align: center;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator big,
+.mq-math-mode .mq-large-operator .mq-to {
+ display: block;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator .mq-to {
+ font-size: 80%;
+}
+.mq-math-mode .mq-large-operator .mq-from {
+ float: right;
+ /* take out of normal flow to manipulate baseline */
+ width: 100%;
+}
+.mq-math-mode,
+.mq-math-mode .mq-editable-field {
+ cursor: text;
+ font-family: Symbola, 'Times New Roman', serif;
+}
+.mq-math-mode .mq-overarrow {
+ border-top: 1px solid black;
+ margin-top: 1px;
+ padding-top: 0.2em;
+}
+.mq-math-mode .mq-overarrow:before {
+ display: block;
+ position: relative;
+ top: -0.34em;
+ font-size: 0.5em;
+ line-height: 0em;
+ content: '\27A4';
+ text-align: right;
+}
+.mq-math-mode .mq-overarrow.mq-arrow-left:before {
+ -moz-transform: scaleX(-1);
+ -o-transform: scaleX(-1);
+ -webkit-transform: scaleX(-1);
+ transform: scaleX(-1);
+ filter: FlipH;
+ -ms-filter: 'FlipH';
+}
+.mq-math-mode .mq-selection,
+.mq-editable-field .mq-selection,
+.mq-math-mode .mq-selection .mq-non-leaf,
+.mq-editable-field .mq-selection .mq-non-leaf,
+.mq-math-mode .mq-selection .mq-scaled,
+.mq-editable-field .mq-selection .mq-scaled {
+ background: #b4d5fe !important;
+ background: Highlight !important;
+ color: HighlightText;
+ border-color: HighlightText;
+}
+.mq-math-mode .mq-selection .mq-matrixed,
+.mq-editable-field .mq-selection .mq-matrixed {
+ background: #39f !important;
+}
+.mq-math-mode .mq-selection .mq-matrixed-container,
+.mq-editable-field .mq-selection .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='#3399FF') !important;
+}
+.mq-math-mode .mq-selection.mq-blur,
+.mq-editable-field .mq-selection.mq-blur,
+.mq-math-mode .mq-selection.mq-blur .mq-non-leaf,
+.mq-editable-field .mq-selection.mq-blur .mq-non-leaf,
+.mq-math-mode .mq-selection.mq-blur .mq-scaled,
+.mq-editable-field .mq-selection.mq-blur .mq-scaled,
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed {
+ background: #d4d4d4 !important;
+ color: black;
+ border-color: black;
+}
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed-container,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='#D4D4D4') !important;
+}
+.mq-editable-field .mq-textarea,
+.mq-math-mode .mq-textarea {
+ position: relative;
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+}
+.mq-editable-field .mq-textarea *,
+.mq-math-mode .mq-textarea *,
+.mq-editable-field .mq-selectable,
+.mq-math-mode .mq-selectable {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+ position: absolute;
+ clip: rect(1em 1em 1em 1em);
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ -ms-transform: scale(0);
+ -o-transform: scale(0);
+ transform: scale(0);
+ resize: none;
+ width: 1px;
+ height: 1px;
+}
+.mq-math-mode .mq-matrixed {
+ background: white;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='white');
+ margin-top: -0.1em;
+}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
new file mode 100644
index 000000000..b4102e08e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -0,0 +1,87 @@
+import React, { Component, createRef } from 'react';
+
+// Import JQuery, required for the functioning of the equation editor
+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');
+
+(window as any).MathQuill = (window as any).MathQuill.getInterface(1);
+
+type EquationEditorProps = {
+ onChange(latex: string): void;
+ value: string;
+ spaceBehavesLikeTab?: boolean;
+ autoCommands: string;
+ autoOperatorNames: string;
+ onEnter?(): void;
+};
+
+/**
+ * @typedef {EquationEditorProps} props
+ * @prop {Function} onChange Triggered when content of the equation editor changes
+ * @prop {string} value Content of the equation handler
+ * @prop {boolean}[false] spaceBehavesLikeTab Whether spacebar should simulate tab behavior
+ * @prop {string} autoCommands List of commands for which you only have to type the name of the
+ * command with a \ in front of it. Examples: pi theta rho sum
+ * @prop {string} autoOperatorNames List of operators for which you only have to type the name of the
+ * operator with a \ in front of it. Examples: sin cos tan
+ * @prop {Function} onEnter Triggered when enter is pressed in the equation editor
+ * @extends {Component<EquationEditorProps>}
+ */
+class EquationEditor extends Component<EquationEditorProps> {
+ element: any;
+ mathField: any;
+ ignoreEditEvents: number;
+
+ // Element needs to be in the class format and thus requires a constructor. The steps that are run
+ // in the constructor is to make sure that React can succesfully communicate with the equation
+ // editor.
+ constructor(props: any) {
+ super(props);
+
+ this.element = createRef();
+ this.mathField = null;
+
+ // MathJax apparently fire 2 edit events on startup.
+ this.ignoreEditEvents = 2;
+ }
+
+ componentDidMount() {
+ const { onChange, value, spaceBehavesLikeTab, autoCommands, autoOperatorNames, onEnter } = this.props;
+
+ const config = {
+ handlers: {
+ edit: () => {
+ if (this.ignoreEditEvents > 0) {
+ this.ignoreEditEvents -= 1;
+ return;
+ }
+ if (this.mathField.latex() !== value) {
+ onChange(this.mathField.latex());
+ }
+ },
+ enter: onEnter,
+ },
+ spaceBehavesLikeTab,
+ autoCommands,
+ autoOperatorNames,
+ };
+
+ this.mathField = (window as any).MathQuill.MathField(this.element.current, config);
+ this.mathField.latex(value || '');
+ }
+
+ render() {
+ return <span ref={this.element} style={{ border: '0px', boxShadow: 'None' }} />;
+ }
+}
+
+export default EquationEditor;
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 5e62d94c2..331ed1980 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,4 +1,4 @@
-import EquationEditor from 'equation-editor-react';
+import EquationEditor from './EquationEditor';
import { IReactionDisposer, trace } from 'mobx';
import { observer } from 'mobx-react';
import { TextSelection } from 'prosemirror-state';
@@ -7,7 +7,8 @@ import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import React = require('react');
+import * as React from 'react';
+import { AnyArray } from 'mongoose';
export class EquationView {
dom: HTMLDivElement; // container for label and value
@@ -63,7 +64,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
_fieldKey: string;
_ref: React.RefObject<EquationEditor> = React.createRef();
- constructor(props: IEquationViewInternal) {
+ constructor(props: any) {
super(props);
this._fieldKey = this.props.fieldKey;
this._textBoxDoc = this.props.tbox.props.Document;
@@ -101,7 +102,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
<EquationEditor
ref={this._ref}
value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
- onChange={str => (this._textBoxDoc[this._fieldKey] = str)}
+ onChange={(str: any) => (this._textBoxDoc[this._fieldKey] = str)}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
spaceBehavesLikeTab={true}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 717efd5a9..03ff0436b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
.ProseMirror {
width: 100%;
@@ -592,7 +592,7 @@ footnote::before {
}
@media only screen and (max-width: 1000px) {
- @import '../../global/globalCssVariables';
+ @import '../../global/globalCssVariables.module.scss';
.ProseMirror {
width: 100%;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b65c440d1..ad2fab8b0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,9 +1,8 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { setCORS } from 'google-translate-api-browser';
+import { Tooltip } from '@mui/material';
import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -56,7 +55,7 @@ import { StyleProp } from '../../StyleProvider';
import { media_state } from '../AudioBox';
import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import { LinkDocPreview } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
import { PinProps, PresBox } from '../trails';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
@@ -71,10 +70,8 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
-import applyDevTools = require('prosemirror-dev-tools');
-import React = require('react');
-// setting up cors-anywhere server address
-const translate = setCORS('http://cors-anywhere.herokuapp.com/');
+// import * as applyDevTools from 'prosemirror-dev-tools';
+import * as React from 'react';
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -110,6 +107,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _recordingStart: number = 0;
private _ignoreScroll = false;
private _lastText = '';
+ private _hadDownFocus = false;
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
@@ -117,6 +115,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _forceDownNode: Node | undefined;
private _downX = 0;
private _downY = 0;
+ private _downTime = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
public get EditorView() {
@@ -130,7 +129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get noSidebar() {
- return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._layout_noSidebar;
+ return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
}
@computed get layout_sidebarWidthPercent() {
return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -139,19 +138,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@computed get layout_autoHeight() {
- return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight;
+ return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight;
}
@computed get textHeight() {
- return NumCast(this.rootDoc[this.fieldKey + '_height']);
+ return NumCast(this.dataDoc[this.fieldKey + '_height']);
}
@computed get scrollHeight() {
- return NumCast(this.rootDoc[this.fieldKey + '_scrollHeight']);
+ return NumCast(this.dataDoc[this.fieldKey + '_scrollHeight']);
}
@computed get sidebarHeight() {
- return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '_height']);
+ return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.SidebarKey + '_height']);
}
@computed get titleHeight() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
}
@computed get layout_autoHeightMargins() {
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
@@ -163,8 +162,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
}
@computed get config() {
- this._keymap = buildKeymap(schema, this.props);
- this._rules = new RichTextRules(this.rootDoc, this);
+ this._keymap = buildKeymap(schema, this._props);
+ this._rules = new RichTextRules(this.Document, this);
return {
schema,
plugins: [
@@ -188,7 +187,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private gptRes: string = '';
public static PasteOnLoad: ClipboardEvent | undefined;
- public static SelectOnLoad = '';
+ private static SelectOnLoad: Doc | undefined;
+ public static SetSelectOnLoad(doc: Doc) {
+ FormattedTextBox.SelectOnLoad = doc;
+ }
public static DontSelectInitialText = false; // whether initial text should be selected or not
public static SelectOnLoadChar = '';
public static IsFragment(html: string) {
@@ -208,6 +210,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
constructor(props: any) {
super(props);
+ makeObservable(this);
FormattedTextBox.Instance = this;
this._recordingStart = Date.now();
}
@@ -247,13 +250,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc;
- const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc });
+ if (!pinProps && this._editorView?.state.selection.empty) return this.Document;
+ const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
this.addDocument(anchor);
this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
this._finishingLink = false;
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.Document);
return anchor;
};
@@ -308,15 +311,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e.preventDefault();
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
- FormattedTextBox.SelectOnLoad = target[Id];
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
+ FormattedTextBox.SetSelectOnLoad(target);
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
- this.props.isSelected() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
+ this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
let ele: Opt<HTMLDivElement> = undefined;
try {
const contents = window.getSelection()?.getRangeAt(0).cloneContents();
@@ -337,7 +340,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
- const templateData = this.rootDoc !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
+ 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 effectiveAcl = GetEffectiveAcl(dataDoc);
@@ -359,11 +362,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+ if ((this._finishingLink || this._props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
- textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
+ textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
unchanged = false;
}
} else {
@@ -371,7 +374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
dataDoc[this.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data)));
dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have
- ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
+ ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
this._applyingChange = '';
@@ -388,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView.updateState(EditorState.fromJSON(this.config, json));
}
}
- if (window.getSelection()?.isCollapsed && this.props.isSelected()) {
+ if (window.getSelection()?.isCollapsed && this._props.rootSelected?.()) {
AnchorMenu.Instance.fadeOut(true);
}
}
@@ -436,7 +439,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
if (this._editorView?.state.doc.textContent) {
const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
@@ -448,17 +451,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
- oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
+ oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
};
updateTitle = () => {
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
- !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
(title.startsWith('-') || title.startsWith('@')) &&
this._editorView &&
!this.dataDoc.title_custom &&
- (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text')
+ (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
) {
let node = this._editorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
@@ -469,7 +472,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (!(cfield instanceof ComputedField)) {
this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
if (str.startsWith('@') && str.length > 1) {
- Doc.AddToMyPublished(this.rootDoc);
+ Doc.AddToMyPublished(this.Document);
}
}
}
@@ -478,7 +481,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
- if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
+ if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
var alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
@@ -489,15 +492,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
- (LinkManager.Links(this.rootDoc).find(
+ (LinkManager.Links(this.Document).find(
link =>
- Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && //
+ Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.Document) && //
Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)
) ||
- DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!);
+ DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
newAutoLinks.add(alink);
// DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
- const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -566,7 +569,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData) {
de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
@@ -576,13 +578,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (dragData) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
const effectiveAcl = GetEffectiveAcl(dataDoc);
- let added = [AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl);
const draggedDoc = dragData.draggedDocuments.lastElement();
- if (added) {
+ let added: Opt<boolean>;
+ if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
// replace text contents when dragging with Alt
if (de.altKey) {
const fieldKey = Doc.LayoutFieldKey(draggedDoc);
- if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+ if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
}
@@ -600,7 +602,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
if (added) {
draggedDoc._freeform_fitContentsToBox = true;
- Doc.SetContainer(draggedDoc, this.rootDoc);
+ Doc.SetContainer(draggedDoc, this.Document);
const view = this._editorView!;
try {
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
@@ -613,8 +615,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
} // otherwise, fall through to outer collection to handle drop
- !added && e.preventDefault();
- e.stopPropagation();
+ added === false && e.preventDefault();
+ added === true && e.stopPropagation();
return added;
}
return false;
@@ -740,9 +742,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
const width = NumCast(this.layoutDoc._width) + localDelta[0];
@@ -756,15 +758,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
deleteAnnotation = (anchor: Doc) => {
const batch = UndoManager.StartBatch('delete link');
LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
- // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]);
- // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
+ // const docAnnotations = DocListCast(this._props.dataDoc[this.fieldKey]);
+ // this._props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
// AnchorMenu.Instance.fadeOut(true);
- this.props.select(false);
+ this._props.select(false);
setTimeout(batch.end); // wait for reaction to remove link from document
};
@undoBatch
- pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
+ pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
@undoBatch
makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
@@ -774,7 +776,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
- this.props.addDocTab(trail, OpenWhere.replaceRight);
+ this._props.addDocTab(trail, OpenWhere.replaceRight);
}
};
@@ -792,7 +794,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
?.trim()
.split(' ')
.filter(h => h);
- const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0];
+ const anchorDoc = Array.from(hrefs ?? [])
+ .lastElement()
+ .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));
@@ -821,7 +826,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
changeItems.push({
description: 'plain',
event: undoBatch(() => {
- Doc.setNativeView(this.rootDoc);
+ Doc.setNativeView(this.Document);
this.layoutDoc.layout_autoHeightMargins = undefined;
}),
icon: 'eye',
@@ -830,8 +835,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
description: 'metadata',
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.rootDoc.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._layout_autoHeightMargins = 50), 50);
+ this.Document.layout_fieldKey = 'layout_meta';
+ setTimeout(() => (this.layoutDoc._headerHeight = this.layoutDoc._layout_autoHeightMargins = 50), 50);
}),
icon: 'eye',
});
@@ -842,8 +847,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
description: StrCast(note.title),
event: undoBatch(() => {
this.layoutDoc.layout_autoHeightMargins = undefined;
- Doc.setNativeView(this.rootDoc);
- DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+ Doc.setNativeView(this.Document);
+ DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
}),
icon: icon,
});
@@ -882,40 +887,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
!Doc.noviceMode &&
appearanceItems.push({
description: 'Broadcast Message',
- event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[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',
});
appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
- // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Make Default Layout',
event: () => {
if (!this.layoutDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = 'text';
- MakeTemplate(this.rootDoc, true, title);
- } else if (!this.rootDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = 'text';
- this.rootDoc.layout = this.layoutDoc.layout as string;
- this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
- this.rootDoc.isTemplateDoc = false;
- this.rootDoc.isTemplateForField = '';
- this.rootDoc.layout_fieldKey = 'layout';
- MakeTemplate(this.rootDoc, true, title);
- setTimeout(() => {
- this.rootDoc._layout_autoHeight = this.layoutDoc._layout_autoHeight; // layout_autoHeight, width and height
- this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
- this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
- this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null);
- this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null);
- }, 10);
+ const title = StrCast(this.Document.title);
+ this.Document.title = 'text';
+ MakeTemplate(this.Document, true, title);
}
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
- Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.rootDoc);
+ Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
+ Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
},
icon: 'eye',
});
@@ -925,7 +913,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
- this.props.renderDepth &&
+ 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),
@@ -969,8 +957,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
generateImage = async () => {
GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
- GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
- GPTPopup.Instance.addToCollection = this.props.addDocument;
+ GPTPopup.Instance?.setImgTargetDoc(this.Document);
+ GPTPopup.Instance.addToCollection = this._props.addDocument;
GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
GPTPopup.Instance.generateImage();
};
@@ -1069,13 +1057,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
anchor.presentation_zoomText = true;
return anchor;
}
- return anchorDoc ?? this.rootDoc;
+ return anchorDoc ?? this.Document;
}
- return anchorDoc ?? this.rootDoc;
+ return anchorDoc ?? this.Document;
}
getView = async (doc: Doc) => {
- if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
!this.SidebarShown && this.toggleSidebar(false);
setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
}
@@ -1139,7 +1127,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
return focusSpeed;
} else {
- return this.props.focus(this.rootDoc, options);
+ return this._props.focus(this.Document, options);
}
}
};
@@ -1148,15 +1136,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + '_height'] = scrollHeight;
+ this.dataDoc[this.fieldKey + '_height'] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
};
@computed get contentScaling() {
- return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1;
+ return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
}
componentDidMount() {
- !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+ !this._props.dontSelectOnLoad && this._props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
@@ -1169,7 +1157,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
{ fireImmediately: true }
);
this._disposers.width = reaction(
- () => this.props.PanelWidth(),
+ () => this._props.PanelWidth(),
width => this.tryUpdateScrollHeight()
);
this._disposers.scrollHeight = reaction(
@@ -1183,13 +1171,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
- (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this.props.isSelected()) && //
+ (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
layout_autoHeight &&
newHeight &&
- newHeight !== this.rootDoc.height &&
- !this.props.dontRegisterView
+ newHeight !== this.layoutDoc.height &&
+ !this._props.dontRegisterView
) {
- this.props.setHeight?.(newHeight);
+ this._props.setHeight?.(newHeight);
}
},
{ fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
@@ -1231,7 +1219,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
);
this._disposers.pullDoc = reaction(
- () => this.props.Document[Pulls],
+ () => this._props.Document[Pulls],
() => {
if (!DocumentButtonBar.hasPulledHack) {
DocumentButtonBar.hasPulledHack = true;
@@ -1240,7 +1228,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
);
this._disposers.pushDoc = reaction(
- () => this.props.Document[Pushes],
+ () => this._props.Document[Pushes],
() => {
if (!DocumentButtonBar.hasPushedHack) {
DocumentButtonBar.hasPushedHack = true;
@@ -1250,30 +1238,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
this._disposers.search = reaction(
- () => Doc.IsSearchMatch(this.rootDoc),
+ () => Doc.IsSearchMatch(this.Document),
search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }
+ { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false }
);
this._disposers.selected = reaction(
- () => this.props.isSelected(),
+ () => this._props.rootSelected?.(),
action(selected => {
//selected && setTimeout(() => this.prepareForTyping());
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
- RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
if (this._editorView && selected) {
- RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
setTimeout(this.autoLink, 20);
}
}),
{ fireImmediately: true }
);
- if (!this.props.dontRegisterView) {
+ if (!this._props.dontRegisterView) {
this._disposers.record = reaction(
() => this._recordingDictation,
() => {
@@ -1288,7 +1276,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
pos => {
- if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
+ if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -1320,7 +1308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this._editorView && reference) {
const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
- response && (this.dataDoc[GoogleRef] = response.documentId);
+ response?.documentId && (this.dataDoc[GoogleRef] = response.documentId);
const pushSuccess = response !== undefined && !('errors' in response);
dataDoc.googleDocUnchanged = pushSuccess;
DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
@@ -1418,7 +1406,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const dashField = view.state.schema.nodes.paragraph.create({}, [
view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
view.state.schema.marks.linkAnchor.create({
- allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
+ allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
noPreview: true,
docref: false,
@@ -1428,7 +1416,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
]),
]);
- const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { link_relationship: 'PDF pasted' });
+ const link = DocUtils.MakeLink(pdfAnchor, this.Document, { link_relationship: 'PDF pasted' });
if (link) {
view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
}
@@ -1451,8 +1439,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const self = this;
return new Plugin({
view(newView) {
- runInAction(() => self.props.isSelected() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
- return new RichTextMenuPlugin({ editorProps: this.props });
+ runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+ return new RichTextMenuPlugin({ editorProps: this._props });
},
});
}
@@ -1474,7 +1462,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
- const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
+ const scrollPos = scrollRef.scrollTop + shift * self._props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
} else {
@@ -1526,11 +1514,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
(this._editorView as any).TextView = this;
}
- const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
- if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+ if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
- FormattedTextBox.SelectOnLoad = '';
- this.props.select(false);
+ FormattedTextBox.SelectOnLoad = undefined;
+ 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) });
@@ -1546,7 +1534,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
selectOnLoad && this._editorView!.focus();
- if (this.props.isContentActive()) this.prepareForTyping();
+ if (this._props.isContentActive()) this.prepareForTyping();
if (this._editorView) {
const tr = this._editorView.state.tr;
const { from, to } = tr.selection;
@@ -1569,15 +1557,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
+ ...(Doc.UserDoc().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));
};
- @action
componentWillUnmount() {
if (this._recordingDictation) {
this._recordingDictation = !this._recordingDictation;
@@ -1588,7 +1575,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
FormattedTextBox.LiveTextUndo = undefined;
this.unhighlightSearchTerms();
this._editorView?.destroy();
- RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
+ RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
}
@@ -1610,7 +1597,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, OpenWhere.addBottom);
+ this._props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
} else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
@@ -1623,8 +1610,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
this._downX = e.clientX;
this._downY = e.clientY;
+ this._downTime = Date.now();
+ this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
FormattedTextBoxComment.textBox = this;
- if (e.button === 0 && (this.props.rootSelected() || this.props.isSelected()) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
@@ -1641,9 +1630,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerUp = (e: React.PointerEvent): void => {
const editor = this._editorView!;
const state = editor?.state;
+ if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime) && !this._hadDownFocus) {
+ (this.ProseRef?.children[0] as HTMLElement)?.blur?.();
+ }
if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- else if (this.props.isContentActive(true)) {
+ else if (this._props.isContentActive(true)) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let xpos = pcords?.pos || 0;
while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1658,7 +1650,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
onDoubleClick = (e: React.MouseEvent): void => {
FormattedTextBoxComment.textBox = this;
- if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar
e.stopPropagation(); // if the text box is selected, then it consumes all click events
@@ -1669,7 +1661,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
FormattedTextBoxComment.Hide();
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ if (e.buttons === 1 && this._props.rootSelected?.() && !e.altKey) {
e.stopPropagation();
}
};
@@ -1681,12 +1673,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
onFocused = (e: React.FocusEvent): void => {
//applyDevTools.applyDevTools(this._editorView);
- this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
onClick = (e: React.MouseEvent): void => {
- if (!this.props.isContentActive()) return;
+ if (!this._props.isContentActive()) return;
if ((e.nativeEvent as any).handledByInnerReactInstance) {
e.stopPropagation();
return;
@@ -1715,7 +1707,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
- if (this.props.isSelected()) {
+ 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);
@@ -1729,7 +1721,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
let olistPos = clickPos?.pos;
- if (clickPos && olistPos && this.props.isSelected()) {
+ if (clickPos && olistPos && this._props.rootSelected?.()) {
const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
@@ -1757,7 +1749,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
startUndoTypingBatch() {
- !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('text edits on ' + this.rootDoc.title));
+ !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('text edits on ' + this.Document.title));
}
public endUndoTypingBatch() {
this._undoTyping?.end();
@@ -1778,8 +1770,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
tr && this._editorView.dispatch(tr);
}
}
- if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected()) {
- RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
+ if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
this.endUndoTypingBatch();
@@ -1789,29 +1781,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const state = this._editorView!.state;
const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
- if (this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
- try {
- translate(curText, { from: 'en', to: 'es' }).then((result1: any) => {
- setTimeout(
- () =>
- translate(result1.text, { from: 'es', to: 'en' }).then((result: any) => {
- const tb = this._sidebarTagRef.current as FormattedTextBox;
- tb._editorView?.dispatch(tb._editorView!.state.tr.insertText(result1.text + '\r\n\r\n' + result.text));
- }),
- 1000
- );
- });
- } catch (e: any) {
- console.log(e.message);
- }
- this._lastText = curText;
- }
- if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc.title_custom) {
+ if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
UndoManager.RunInBatch(() => {
this.dataDoc.title_custom = true;
this.dataDoc.layout_showTitle = 'title';
const tr = this._editorView!.state.tr;
- this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
+ this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection());
}, 'titler');
}
};
@@ -1820,7 +1795,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if ((e.altKey || e.ctrlKey) && e.key === 't') {
e.preventDefault();
e.stopPropagation();
- this.props.setTitleFocus?.();
+ this._props.setTitleFocus?.();
return;
}
const state = this._editorView!.state;
@@ -1837,7 +1812,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let stopPropagation = true;
for (var 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.rootDoc))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
e.preventDefault();
}
}
@@ -1846,7 +1821,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
- RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
+ RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
@@ -1860,7 +1835,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
case ' ':
if (e.code !== 'Space') {
- [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
+ [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) })));
}
break;
@@ -1873,8 +1848,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
};
onScroll = (e: React.UIEvent) => {
- if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
- if (!this.props.dontSelectOnLoad) {
+ if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
+ if (!this._props.dontSelectOnLoad) {
this._ignoreScroll = true;
this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
this._ignoreScroll = false;
@@ -1884,9 +1859,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
};
tryUpdateScrollHeight = () => {
- const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+ 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.GetIsDragging()) {
+ if (children && !SnappingManager.IsDragging) {
const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
const toHgt = (node: Element) => {
const { height, marginTop, marginBottom } = getComputedStyle(node);
@@ -1894,11 +1869,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
- if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) {
+ if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => (this.rootDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
+ const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
- if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
+ if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
} else {
setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
@@ -1906,21 +1881,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
};
- fitContentsToBox = () => BoolCast(this.props.Document._freeform_fitContentsToBox);
- sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+ sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '_height'] = height);
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ 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
+ this._props
.ScreenToLocalTransform()
- .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
- .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
+ .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
+ .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
return !this._recordingDictation ? null : (
@@ -1943,9 +1918,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
- return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
+ return !annotated && (!this._props.isContentActive() || SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? null : (
<div
className="formattedTextBox-sidebar-handle"
onPointerDown={this.sidebarDown}
@@ -1960,12 +1935,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
- rootDoc={this.rootDoc}
+ {...this._props}
+ Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth={true}
@@ -1981,14 +1956,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setHeight={this.setSidebarHeight}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...this.props}
+ {...this._props}
ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
@@ -2002,7 +1977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
moveDocument={this.sidebarMoveDocument}
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
@@ -2020,12 +1995,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
cycleAlternateText = () => {
if (this.layoutDoc._layout_enableAltContentUI) {
- const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`];
- this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
}
};
@computed get overlayAlternateIcon() {
- const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`];
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
return (
<Tooltip
title={
@@ -2047,7 +2022,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-alternateButton"
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
style={{
- display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
+ display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
color: usePath === undefined ? 'black' : 'white',
}}>
@@ -2056,14 +2031,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
</Tooltip>
);
}
- @computed get fieldKey() {
- const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}_usePath`]);
- return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this.props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
+ get fieldKey() {
+ const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+ return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
}
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
- if (this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
+ if (this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
// if the scrolled freeform is a child of the sidebar component, we need to let the event go through
// so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
e.preventDefault();
@@ -2072,11 +2047,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
// 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()) {
+ 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._headerHeight}px' >
+ const height = Number(styleFromLayoutString.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1)) && //
- (this.props.isSelected() || this.isAnyChildContentActive())
+ (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ (this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
}
@@ -2085,25 +2063,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
_oldWheel: any;
@computed get fontColor() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
}
@computed get fontSize() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
}
@computed get fontFamily() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
}
@computed get fontWeight() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight);
}
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
- 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.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ 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._headerHeight}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox"
@@ -2115,7 +2093,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
}}
style={{
- ...(this.props.dontScale
+ ...(this._props.dontScale
? {}
: {
transform: `scale(${scale})`,
@@ -2134,9 +2112,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-cont"
ref={this._ref}
style={{
- cursor: this.props.isContentActive() ? 'text' : undefined,
- height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
- pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
+ cursor: this._props.isContentActive() ? 'text' : undefined,
+ height: this._props.height ? 'max-content' : undefined,
+ pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -2151,7 +2129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-outer"
ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2168,8 +2146,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}}
/>
</div>
- {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+ {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index e7ca26d5c..be8736525 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -3,7 +3,7 @@ import { EditorState, NodeSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
-import { LinkDocPreview } from '../LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
import { schema } from './schema_rts';
@@ -133,9 +133,9 @@ export class FormattedTextBoxComment {
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
//nbef &&
naft &&
- LinkDocPreview.SetLinkInfo({
+ LinkInfo.SetLinkInfo({
docProps: textBox.props,
- linkSrc: textBox.rootDoc,
+ linkSrc: textBox.Document,
linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
hrefs,
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index ec11079b4..08bad2d57 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -47,7 +47,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.DataDoc)) {
+ switch (GetEffectiveAcl(props.TemplateDataDocument)) {
case AclAugment:
const prevNode = state.selection.$cursor.nodeBefore;
const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
@@ -334,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.DataDoc && GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true;
+ if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 8afa0f6b5..d6ed5ebee 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables.module.scss';
.button-dropdown-wrapper {
position: relative;
@@ -55,7 +55,6 @@
input {
color: black;
}
-
}
.richTextMenu {
@@ -68,12 +67,12 @@
font-size: 12px;
height: 100%;
margin-right: 3px;
-
+
&:focus,
&:hover {
background-color: black;
}
-
+
&::-ms-expand {
color: white;
}
@@ -126,7 +125,7 @@
display: flex;
justify-content: space-between;
- >div {
+ > div {
display: flex;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 712be91c8..4881070fd 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,27 +1,28 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { 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 { 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 { Cast, StrCast } from '../../../../fields/Types';
+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';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
-import { EquationBox } from '../EquationBox';
-import { numberRange } from '../../../../Utils';
const { toggleMark } = require('prosemirror-commands');
@observer
@@ -30,6 +31,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
+ layoutDoc: Doc | undefined;
@observable public view?: EditorView;
public editorProps: FieldViewProps | undefined;
@@ -62,14 +64,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private showLinkDropdown: boolean = false;
_reaction: IReactionDisposer | undefined;
- constructor(props: Readonly<{}>) {
+ constructor(props: AntimodeMenuProps) {
super(props);
- runInAction(() => {
- RichTextMenu.Instance = this;
- this.updateMenu(undefined, undefined, props);
- this._canFade = false;
- this.Pinned = true;
- });
+ makeObservable(this);
+ RichTextMenu.Instance = this;
+ this.updateMenu(undefined, undefined, props, this.layoutDoc);
+ this._canFade = false;
+ this.Pinned = true;
}
@computed get noAutoLink() {
@@ -102,11 +103,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get textAlign() {
return this._activeAlignment;
}
+ @computed get textVcenter() {
+ return BoolCast(this.layoutDoc?.layout_centered);
+ }
_disposer: IReactionDisposer | undefined;
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views().slice(),
- views => this.updateMenu(undefined, undefined, undefined)
+ () => SelectionManager.Views.slice(),
+ views => this.updateMenu(undefined, undefined, undefined, undefined)
);
}
componentWillUnmount() {
@@ -114,11 +118,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
+ public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any, layoutDoc: Doc | undefined) {
if (this._linkToRef.current?.getBoundingClientRect().width) {
return;
}
this.view = view;
+ this.layoutDoc = layoutDoc;
props && (this.editorProps = props);
// Don't do anything if the document/selection didn't change
@@ -181,7 +186,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// finds font sizes and families in selection
getActiveAlignment() {
- if (this.view && this.TextView?.props.isSelected()) {
+ if (this.view && this.TextView?.props.rootSelected?.()) {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
@@ -194,7 +199,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// finds font sizes and families in selection
getActiveListStyle() {
- if (this.view && this.TextView?.props.isSelected()) {
+ if (this.view && this.TextView?.props.rootSelected?.()) {
const path = (this.view.state.selection.$from as any).path;
for (let i = 0; i < path.length; i += 3) {
if (path[i].type === this.view.state.schema.nodes.ordered_list) {
@@ -214,7 +219,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeSizes = new Set<string>();
const activeColors = new Set<string>();
const activeHighlights = new Set<string>();
- if (this.view && this.TextView?.props.isSelected()) {
+ if (this.view && this.TextView?.props.rootSelected?.()) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
const marks: Mark[] = [...(state.storedMarks ?? [])];
@@ -233,8 +238,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
});
- } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._text_fontSize) && activeSizes.add(StrCast(dv.rootDoc._text_fontSize)));
+ } 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)));
}
return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
}
@@ -249,7 +254,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
let activeMarks: MarkType[] = [];
- if (!this.view || !this.TextView?.props.isSelected()) return activeMarks;
+ if (!this.view || !this.TextView?.props.rootSelected?.()) return activeMarks;
const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
@@ -285,10 +290,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return activeMarks;
}
- destroy() {
- !this.TextView?.props.isSelected() && this.fadeOut(true);
- }
-
@action
setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
if (!activeMarks) return;
@@ -357,10 +358,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
}
- } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views().forEach(dv => (dv.rootDoc._text_fontSize = fontSize));
+ } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
+ SelectionManager.Views.forEach(dv => (dv.Document._text_fontSize = fontSize));
} else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props);
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
setFontFamily = (family: string) => {
@@ -369,7 +370,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
} else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props);
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
setHighlight(color: string) {
@@ -378,7 +379,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
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.updateMenu(this.view, undefined, this.props, this.layoutDoc);
}
setColor(color: string) {
@@ -387,7 +388,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
this.view.focus();
} else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props);
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
}
// TODO: remove doesn't work
@@ -428,7 +429,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
insertSummarizer(state: EditorState, dispatch: any) {
@@ -442,8 +443,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
+ vcenterToggle = (view: EditorView, dispatch: any) => {
+ this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered);
+ };
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
- if (this.TextView?.props.isSelected()) {
+ 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) => {
if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
@@ -638,7 +642,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
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 currentDoc = SelectionManager.Docs().lastElement();
+ 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);
@@ -665,7 +669,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
};
@undoBatch
- @action
deleteLink = () => {
if (this.view) {
const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
@@ -771,11 +774,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// <div className="collectionMenu-divider" key="divider 3" />
// {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
// this.activeFontSize = val;
- // SelectionManager.Views().map(dv => dv.props.Document._text_fontSize = val);
+ // SelectionManager.Views.map(dv => dv.props.Document._text_fontSize = val);
// })),
// this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
// this.activeFontFamily = val;
- // SelectionManager.Views().map(dv => dv.props.Document._text_fontFamily = val);
+ // SelectionManager.Views.map(dv => dv.props.Document._text_fontFamily = val);
// })),
// <div className="collectionMenu-divider" key="divider 4" />,
// this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
@@ -808,10 +811,15 @@ interface ButtonDropdownProps {
}
@observer
-export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps> {
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
document.addEventListener('pointerdown', this.onBlur);
}
@@ -846,22 +854,22 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
render() {
return (
<div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
- {!this.props.pdf ? (
- <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
- {this.props.button}
- <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ {!this._props.pdf ? (
+ <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ {this._props.button}
+ <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
) : (
<>
- {this.props.button}
+ {this._props.button}
<button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
</>
)}
- {this.showDropdown ? this.props.dropdownContent : null}
+ {this.showDropdown ? this._props.dropdownContent : null}
</div>
);
}
@@ -875,6 +883,6 @@ export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps>
return null;
}
update(view: EditorView, lastState: EditorState | undefined) {
- RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps);
+ RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 3e2afd2ce..701ee4053 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -95,7 +95,7 @@ export class RichTextRules {
textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
- textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+ textDocInline._textContext = ComputedField.MakeFunction(`copyField(this.${inlineFieldKey})`);
textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
textDoc[inlineFieldKey] = ''; // set a default value for the annotation
const node = (state.doc.resolve(start) as any).nodeAfter;
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 3355e4529..7ec296ed2 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,7 +1,7 @@
import { TextSelection } from 'prosemirror-state';
import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
-import React = require('react');
+import * as React from 'react';
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index 8c4d77da9..87320943d 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -1,9 +1,9 @@
-@import "../views/global/globalCssVariables";
+@import '../views/global/globalCssVariables.module.scss';
.ProseMirror-menu-dropdown-wrap {
display: inline-block;
position: relative;
}
-
+
.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
@@ -17,11 +17,11 @@
margin-right: 4px;
&:after {
- content: "";
+ content: '';
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
- opacity: .6;
+ opacity: 0.6;
position: absolute;
right: 4px;
top: calc(50% - 2px);
@@ -33,7 +33,7 @@
margin-right: -4px;
}
-.ProseMirror-menu-dropdown-menu,
+.ProseMirror-menu-dropdown-menu,
.ProseMirror-menu-submenu {
font-size: 12px;
background: white;
@@ -55,19 +55,18 @@
}
}
-
.ProseMirror-menu-submenu-label:after {
- content: "";
+ content: '';
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentColor;
- opacity: .6;
+ opacity: 0.6;
position: absolute;
right: 4px;
top: calc(50% - 4px);
}
-
- .ProseMirror-icon {
+
+.ProseMirror-icon {
display: inline-block;
// line-height: .8;
// vertical-align: -2px; /* Compensate for padding */
@@ -79,14 +78,14 @@
}
svg {
- fill:white;
+ fill: white;
height: 1em;
}
span {
vertical-align: text-top;
- }
- }
+ }
+}
.wrapper {
position: absolute;
@@ -99,10 +98,10 @@
background: #323232;
border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
}
-.tooltipMenu, .basic-tools {
+.tooltipMenu,
+.basic-tools {
z-index: 3000;
pointer-events: all;
padding: 3px;
@@ -111,17 +110,17 @@
align-items: center;
.ProseMirror-example-setup-style hr {
- padding: 2px 10px;
- border: none;
- margin: 1em 0;
+ padding: 2px 10px;
+ border: none;
+ margin: 1em 0;
}
-
+
.ProseMirror-example-setup-style hr:after {
- content: "";
- display: block;
- height: 1px;
- background-color: silver;
- line-height: 2px;
+ content: '';
+ display: block;
+ height: 1px;
+ background-color: silver;
+ line-height: 2px;
}
}
@@ -142,7 +141,7 @@
}
}
- &> * {
+ & > * {
margin-top: 50%;
margin-left: 50%;
transform: translate(-50%, -50%);
@@ -168,7 +167,7 @@
background-color: black;
}
- &> * {
+ & > * {
margin-top: 50%;
margin-left: 50%;
transform: translate(-50%, -50%);
@@ -208,18 +207,17 @@
margin-top: 13px;
}
- .font-size-indicator {
- font-size: 12px;
- padding-right: 0px;
- }
- .summarize{
- color: white;
- height: 20px;
- text-align: center;
- }
-
-
-.brush{
+.font-size-indicator {
+ font-size: 12px;
+ padding-right: 0px;
+}
+.summarize {
+ color: white;
+ height: 20px;
+ text-align: center;
+}
+
+.brush {
display: inline-block;
width: 1em;
height: 1em;
@@ -229,7 +227,7 @@
margin-right: 15px;
}
-.brush-active{
+.brush-active {
display: inline-block;
width: 1em;
height: 1em;
@@ -269,7 +267,6 @@
}
.buttonSettings-dropdown {
-
&.ProseMirror-menu-dropdown {
width: 10px;
height: 25px;
@@ -301,7 +298,7 @@
padding: 3px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
- .ProseMirror-menu-dropdown-item{
+ .ProseMirror-menu-dropdown-item {
cursor: default;
&:last-child {
@@ -312,7 +309,8 @@
background-color: #323232;
}
- .button-setting, .button-setting-disabled {
+ .button-setting,
+ .button-setting-disabled {
padding: 2px;
border-radius: 2px;
}
@@ -328,25 +326,23 @@
}
input {
- color: black;
- border: none;
- border-radius: 1px;
- padding: 3px;
+ color: black;
+ border: none;
+ border-radius: 1px;
+ padding: 3px;
}
button {
- padding: 6px;
- background-color: #323232;
- border: 1px solid black;
- border-radius: 1px;
+ padding: 6px;
+ background-color: #323232;
+ border: 1px solid black;
+ border-radius: 1px;
&:hover {
- background-color: black;
+ background-color: black;
}
}
}
-
-
}
}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 4faef26e2..a342285b0 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
import { Doc } from '../../../../fields/Doc';
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 4cd2cee52..d023020e1 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 2b0927148..87e1b69c3 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -21,7 +21,7 @@ import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollection
import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import React = require('react');
+import * as React from 'react';
enum BrushStyle {
ADD,
@@ -438,7 +438,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// disable once edited has been clicked (doesn't make sense to change after first edit)
disabled={edited}
checked={isNewCollection}
- onChange={e => {
+ onChange={() => {
setIsNewCollection(prev => !prev);
}}
/>
@@ -513,7 +513,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
defaultValue={150}
size="small"
valueLabelDisplay="auto"
- onChange={(e, val) => {
+ onChange={(e: any, val: any) => {
setCursorData(prev => ({ ...prev, width: val as number }));
}}
/>
@@ -571,7 +571,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
<div>
<TextField
value={input}
- onChange={e => setInput(e.target.value)}
+ onChange={(e: any) => setInput(e.target.value)}
disabled={isBrushing}
type="text"
label="Prompt"
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 10eca358e..185ba2280 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,5 +1,5 @@
import './GenerativeFillButtons.scss';
-import React = require('react');
+import * as React from 'react';
import ReactLoading from 'react-loading';
import { activeColor } from './generativeFillUtils/generativeFillConstants';
import { Button, IconButton, Type } from 'browndash-components';
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 58f0b29e4..9dc0c5180 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -7,7 +7,7 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
import { DefaultStyleProvider } from '../../StyleProvider';
import { DocumentView, DocumentViewInternal } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import React = require('react');
+import * as React from 'react';
@observer
export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -22,9 +22,8 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<DocumentView
{...this.props} //
LayoutTemplateString={undefined}
- Document={this.rootDoc}
+ Document={this.Document}
isContentActive={returnFalse}
- DataDoc={undefined}
addDocument={returnFalse}
ScreenToLocalTransform={this.screenToLocalXf}
hideResizeHandles={true}
@@ -33,6 +32,6 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
render() {
- return !(this.rootDoc instanceof Doc) ? null : this.mainItem;
+ return !(this.Document instanceof Doc) ? null : this.mainItem;
}
}
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 0b51813a5..3b34a1f90 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
.presBox-cont {
cursor: auto;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 789509784..17993d88e 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,7 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Animation } from '../../../../fields/DocSymbols';
@@ -36,8 +36,6 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-const { Howl } = require('howler');
-
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -74,8 +72,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
constructor(props: any) {
super(props);
+ makeObservable(this);
if (!PresBox.navigateToDocScript) {
- PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentation_targetDoc, self)')!;
+ PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
}
}
@@ -114,16 +113,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return StrCast(this.layoutDoc.presFieldKey, 'data');
}
@computed get childDocs() {
- return DocListCast(this.rootDoc[this.presFieldKey]);
+ return DocListCast(this.Document[this.presFieldKey]);
}
@computed get tagDocs() {
return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
}
@computed get itemIndex() {
- return NumCast(this.rootDoc._itemIndex);
+ return NumCast(this.Document._itemIndex);
}
@computed get activeItem() {
- return DocCast(this.childDocs[NumCast(this.rootDoc._itemIndex)]);
+ return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
}
@computed get targetDoc() {
return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
@@ -141,21 +140,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
}
@computed get selectedDocumentView() {
- if (SelectionManager.Views().length) return SelectionManager.Views()[0];
- if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
+ if (SelectionManager.Views.length) return SelectionManager.Views[0];
+ if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
}
@computed get isPres() {
- return this.selectedDoc === this.rootDoc;
+ return this.selectedDoc === this.Document;
}
@computed get selectedDoc() {
- return this.selectedDocumentView?.rootDoc;
+ return this.selectedDocumentView?.Document;
}
isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc;
clearSelectedArray = () => this.selectedArray.clear();
addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
- @action
componentWillUnmount() {
this._unmounting = true;
if (this._presTimer) clearTimeout(this._presTimer);
@@ -170,7 +168,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
() => this.selectedDoc,
selected => {
document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
- (this._presKeyEvents = selected === this.rootDoc) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
+ (this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
},
{ fireImmediately: true }
);
@@ -186,19 +184,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
},
{ fireImmediately: true }
);
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
this._unmounting = false;
- if (this.props.renderDepth > 0) {
- runInAction(() => {
- this.rootDoc._forceRenderEngine = computeTimelineLayout.name;
- this.layoutDoc._gridGap = 0;
- this.layoutDoc._yMargin = 0;
- });
- }
this.turnOffEdit(true);
this._disposers.selection = reaction(
- () => SelectionManager.Views().slice(),
- views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(),
+ () => SelectionManager.Views.slice(),
+ views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(),
{ fireImmediately: true }
);
this._disposers.editing = reaction(
@@ -213,7 +204,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
updateCurrentPresentation = (pres?: Doc) => {
- Doc.ActivePresentation = pres ?? this.rootDoc;
+ Doc.ActivePresentation = pres ?? this.Document;
PresBox.Instance = this;
};
@@ -321,7 +312,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (progressiveReveal(false)) return true;
if (this.childDocs[this.itemIndex + 1] !== undefined) {
// Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
- const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]);
+ const slides = DocListCast(this.Document[StrCast(this.presFieldKey, 'data')]);
const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
// before moving onto next slide, run the subroutines :)
@@ -356,7 +347,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 2: There are no other frames so it should go to the previous slide
prevSelected = Math.max(0, prevSelected - 1);
this.nextSlide(prevSelected);
- this.rootDoc._itemIndex = prevSelected;
+ this.Document._itemIndex = prevSelected;
} else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) {
// Case 3: Pres loop is on so it should go to the last slide
this.nextSlide(this.childDocs.length - 1);
@@ -370,7 +361,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
- this.rootDoc._itemIndex = index;
+ this.Document._itemIndex = index;
if (from?.mediaStopTriggerList && this.layoutDoc.presentation_status !== PresStatus.Edit) {
DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
}
@@ -410,7 +401,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
playAnnotation = (anno: AudioField) => {};
@action
static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
- const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
+ const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
if (!bestTarget) return;
let changed = false;
if (pinDocLayout) {
@@ -441,7 +432,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
if (ffview?.childDocs) {
PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
- ffview.rootDoc._currentFrame = NumCast(activeFrame);
+ ffview.layoutDoc._currentFrame = NumCast(activeFrame);
}
}
}
@@ -600,7 +591,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
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) {
+ if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
if (contentBounds) {
const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -609,7 +600,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dv = DocumentManager.Instance.getDocumentView(bestTarget);
if (dv) {
changed = true;
- const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
+ const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height);
activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
dv.ComponentView?.brushView?.(viewport, transTime, 2500);
}
@@ -754,8 +745,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragViewCache = Array.from(this._dragArray);
const eleViewCache = Array.from(this._eleArray);
const resetSelection = action(() => {
- if (!this.props.isSelected()) {
- const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
+ if (!this._props.isSelected()) {
+ const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
if (presDocView) SelectionManager.SelectView(presDocView, false);
this.clearSelectedArray();
selViewCache.forEach(doc => this.addToSelectedArray(doc));
@@ -897,7 +888,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
});
LightboxView.Instance.SetLightboxDoc(undefined);
- Doc.RemFromMyOverlay(this.rootDoc);
+ Doc.RemFromMyOverlay(this.Document);
return PresStatus.Edit;
};
};
@@ -936,7 +927,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// The function allows for expanding the view of pres on toggle
@action toggleExpandMode = () => {
runInAction(() => (this._expandBoolean = !this._expandBoolean));
- this.rootDoc.expandBoolean = this._expandBoolean;
+ this.Document.expandBoolean = this._expandBoolean;
this.childDocs.forEach(doc => {
doc.presentation_expandInlineButton = this._expandBoolean;
});
@@ -986,16 +977,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@action
enterMinimize = () => {
- this.updateCurrentPresentation(this.rootDoc);
+ this.updateCurrentPresentation(this.Document);
clearTimeout(this._presTimer);
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- this.props.removeDocument?.(this.layoutDoc);
- return PresBox.OpenPresMinimized(this.rootDoc, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
+ const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ this._props.removeDocument?.(this.layoutDoc);
+ return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
};
exitMinimize = () => {
if (Doc.IsInMyOverlay(this.layoutDoc)) {
- Doc.RemFromMyOverlay(this.rootDoc);
- CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right);
+ Doc.RemFromMyOverlay(this.Document);
+ CollectionDockingView.AddSplit(this.Document, OpenWhereMod.right);
}
return PresStatus.Edit;
};
@@ -1021,8 +1012,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === 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.rootDoc._pivotField = undefined);
- this.rootDoc._type_collection = type_collection;
+ [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
+ this.Document._type_collection = type_collection;
if (this.isTreeOrStack) {
this.layoutDoc._gridGap = 0;
}
@@ -1058,7 +1049,7 @@ 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) => {
@@ -1069,11 +1060,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
- this.props.pinToPres(audio, { audioRange: true });
+ this._props.pinToPres(audio, { audioRange: true });
setTimeout(() => this.removeDocument(doc), 0);
return false;
}
- } else {
+ } else if (doc.type !== DocumentType.PRES) {
if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
doc.presentation_targetDoc = doc.createdFrom; // dropped document will be a new embedding of an embedded document somewhere else.
doc.presentation_movement = PresMovement.Zoom;
@@ -1082,10 +1073,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
return true;
};
+
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
- getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
- panelHeight = () => this.props.PanelHeight() - 40;
+ removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
+ getTransform = () => this._props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
+ panelHeight = () => this._props.PanelHeight() - 40;
/**
* For sorting the array so that the order is maintained when it is dropped.
*/
@@ -1123,7 +1115,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
selectPres = () => {
- const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
+ const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
presDocView && SelectionManager.SelectView(presDocView, false);
};
@@ -1139,7 +1131,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (noNav) {
const index = this.childDocs.indexOf(doc);
if (index >= 0 && index < this.childDocs.length) {
- this.rootDoc._itemIndex = index;
+ this.Document._itemIndex = index;
}
} else {
this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
@@ -1237,8 +1229,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case 'ArrowRight':
if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) {
// TODO: update to work properly
- this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1;
- this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
+ this.Document._itemIndex = NumCast(this.Document._itemIndex) + 1;
+ this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
} else {
this.next();
if (this._presTimer) {
@@ -1254,8 +1246,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case 'ArrowLeft':
if (e.shiftKey && this.itemIndex !== 0) {
// TODO: update to work properly
- this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1;
- this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
+ this.Document._itemIndex = NumCast(this.Document._itemIndex) - 1;
+ this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
} else {
this.back();
if (this._presTimer) {
@@ -1318,7 +1310,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else if (doc.config_pinView && presCollection === tagDoc && dv) {
// Case B: Document is presPinView and is presCollection
const scale = 1 / NumCast(doc.config_viewScale);
- const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv.props.PanelWidth(), dv.props.PanelHeight()]);
+ const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv._props.PanelWidth(), dv._props.PanelHeight()]);
const height = (viewBounds[3] - viewBounds[1]) * scale;
const width = (viewBounds[2] - viewBounds[0]) * scale;
const indWidth = width / 10;
@@ -1438,46 +1430,39 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
@undoBatch
- @action
updateHideBefore = (activeItem: Doc) => {
activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
};
@undoBatch
- @action
updateHide = (activeItem: Doc) => {
activeItem.presentation_hide = !activeItem.presentation_hide;
this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
};
@undoBatch
- @action
updateHideAfter = (activeItem: Doc) => {
activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
};
@undoBatch
- @action
updateOpenDoc = (activeItem: Doc) => {
activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
};
@undoBatch
- @action
updateEaseFunc = (activeItem: Doc) => {
activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
};
@undoBatch
- @action
updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
@undoBatch
- @action
updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
static _sliderBatch: any;
@@ -1510,7 +1495,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@undoBatch
- @action
applyTo = (array: Doc[]) => {
this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
@@ -1640,8 +1624,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
let dataField = Doc.LayoutFieldKey(tagDoc);
if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
- if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn?.["${dataField}"]`);
- else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc?.["${dataField}"]`);
+ 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}
/>
@@ -2221,13 +2205,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
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 data = Cast(presCollection?.data, listSpec(Doc));
- const config_data = Cast(this.rootDoc.data, listSpec(Doc));
+ const config_data = Cast(this.Document.data, listSpec(Doc));
if (data && config_data) {
data.push(doc);
- this.props.pinToPres(doc, {});
+ this._props.pinToPres(doc, {});
this.gotoDocument(this.childDocs.length, this.activeItem);
} else {
- this.props.addDocTab(doc, OpenWhere.addRight);
+ this._props.addDocTab(doc, OpenWhere.addRight);
}
}
};
@@ -2288,20 +2272,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed
get toolbarWidth(): number {
- return this.props.PanelWidth();
+ return this._props.PanelWidth();
}
@action
- toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250);
+ toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
@computed get toolbar() {
- const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
- const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
- const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+ 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;
- return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.rootDoc) ? null : (
+ return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
<div id="toolbarContainer" className={'presBox-toolbar'}>
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
<FontAwesomeIcon icon={"plus"} />
@@ -2325,7 +2309,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</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.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2340,12 +2324,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* presentPanel: The button to start the presentation / open minimized view of the presentation
*/
@computed get topPanel() {
- const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+ const mode = StrCast(this.Document._type_collection) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
return (
- <div
- className={`presBox-buttons${Doc.IsInMyOverlay(this.rootDoc) ? ' inOverlay' : ''}`}
- style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
+ <div className={`presBox-buttons${Doc.IsInMyOverlay(this.Document) ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.Document ? Colors.LIGHT_BLUE : undefined }}>
{isMini ? null : (
<select className="presBox-viewPicker" style={{ display: this.layoutDoc.presentation_status === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
<option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
@@ -2373,7 +2355,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
})}>
<FontAwesomeIcon icon={'play-circle'} />
- <div style={{ display: this.props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
+ <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
{mode === CollectionViewType.Carousel3D || isMini ? null : (
<div
@@ -2398,7 +2380,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.itemIndex === this.childDocs.length - 1 &&
(this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
- const inOverlay = Doc.IsInMyOverlay(this.rootDoc);
+ const inOverlay = Doc.IsInMyOverlay(this.Document);
// 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' }}>
@@ -2483,12 +2465,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<b>1</b>
</div>
</Tooltip>
- <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
+ <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this._props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
{inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
{this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider"></div>
- {this.props.PanelWidth() > 250 ? (
+ {this._props.PanelWidth() > 250 ? (
<div
className="presPanel-button-text"
onClick={undoBatch(
@@ -2534,7 +2516,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@undoBatch
- @action
exitClicked = () => {
this.layoutDoc.presentation_status = this._exitTrail?.() ?? this.exitMinimize();
clearTimeout(this._presTimer);
@@ -2556,6 +2537,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
+ SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc);
+
RemFromMap = (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.
if (!this._unmounting && this.isTree) {
@@ -2569,17 +2552,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
// needed to ensure that the childDocs are loaded for looking up fields
this.childDocs.slice();
- const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+ const mode = StrCast(this.Document._type_collection) as CollectionViewType;
const presEnd =
!this.layoutDoc.presLoop &&
this.itemIndex === this.childDocs.length - 1 &&
(this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
- return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
+ 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="presPanelOverlay"
- style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.rootDoc ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+ 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>}>
<div
className="presPanel-button"
@@ -2617,7 +2600,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
) : (
- <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.rootDoc) ? PresBox.minimizedWidth : undefined }}>
+ <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.Document) ? PresBox.minimizedWidth : undefined }}>
{this.topPanel}
{this.toolbar}
{this.newDocumentToolbarDropdown}
@@ -2625,8 +2608,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="Slide">
{mode !== CollectionViewType.Invalid ? (
<CollectionView
- {...this.props}
- PanelWidth={this.props.PanelWidth}
+ {...this._props}
+ PanelWidth={this._props.PanelWidth}
PanelHeight={this.panelHeight}
childIgnoreNativeSize={true}
moveDocument={returnFalse}
@@ -2637,7 +2620,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childOpacity={returnOne}
childClickScript={PresBox.navigateToDocScript}
childLayoutTemplate={this.childLayoutTemplate}
- childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
+ childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
filterAddDocument={this.addDocumentFilter}
removeDocument={returnFalse}
dontRegisterView={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index c667f126f..ec5d090dd 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,7 +1,8 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+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 { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
@@ -24,7 +25,6 @@ import { StyleProp } from '../../StyleProvider';
import { PresBox } from './PresBox';
import './PresElementBox.scss';
import { PresMovement } from './PresEnums';
-import React = require('react');
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
@@ -34,37 +34,58 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(PresElementBox, fieldKey);
}
- _heightDisposer: IReactionDisposer | undefined;
+ private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _titleRef: React.RefObject<EditableView> = React.createRef();
+ private _heightDisposer: IReactionDisposer | undefined;
+ readonly expandViewHeight = 100;
+ readonly collapsedHeight = 35;
- @observable _dragging = false;
- // Idea: this boolean will determine whether to automatically show the video when this preselement is selected.
- // @observable static showVideo: boolean = false;
- @computed get indexInPres() {
- return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
- } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
- @computed get expandViewHeight() {
- return 100;
- }
- @computed get collapsedHeight() {
- return 35;
- } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
- @computed get selectedArray() {
- return this.presBoxView?.selectedArray;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
}
+
+ @observable _dragging = false;
+
+ // the presentation view that renders this slide
@computed get presBoxView() {
- return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
+ return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
}
+
+ // the presentation view document that renders this slide
@computed get presBox() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+ return this.presBoxView?.Document;
}
+
+ // Since this node is being rendered with a template, this method retrieves
+ // the actual slide being rendered from the auto-generated rendering template
+ @computed get slideDoc() {
+ return this._props.TemplateDataDocument ?? this.Document;
+ }
+
+ // this is the document in the workspaces that is targeted by the slide
@computed get targetDoc() {
- return Cast(this.rootDoc.presentation_targetDoc, Doc, null) || this.rootDoc;
+ return Cast(this.slideDoc.presentation_targetDoc, Doc, null) || this.slideDoc;
+ }
+
+ // computes index of this presentation slide in the presBox list
+ @computed get indexInPres() {
+ return this.presBoxView?.SlideIndex(this.slideDoc);
+ }
+
+ @computed get selectedArray() {
+ return this.presBoxView?.selectedArray;
+ }
+
+ @computed get videoRecordingIsInOverlay() {
+ return Doc.MyOverlayDocs.some(doc => doc.slides === this.slideDoc);
}
componentDidMount() {
this.layoutDoc.layout_hideLinkButton = true;
this._heightDisposer = reaction(
- () => ({ expand: this.rootDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
+ () => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
{ fireImmediately: true }
);
@@ -73,55 +94,42 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._heightDisposer?.();
}
- /**
- * Returns a local transformed coordinate array for given coordinates.
- */
- ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
-
- @action
- presExpandDocumentClick = () => (this.rootDoc.presentation_expandInlineButton = !this.rootDoc.presentation_expandInlineButton);
-
- embedHeight = (): number => this.collapsedHeight + this.expandViewHeight;
- // embedWidth = () => this.props.PanelWidth();
- // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
- embedWidth = (): number => this.props.PanelWidth() / 2;
+ 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<DocumentViewProps>, property: string): any => {
- if (property === StyleProp.Opacity) return 1;
- return this.props.styleProvider?.(doc, props, property);
+ return 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.
*/
@computed get renderEmbeddedInline() {
- return !this.rootDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
+ return !this.slideDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
<div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
<DocumentView
- Document={PresBox.targetRenderedDoc(this.rootDoc)}
- DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ Document={PresBox.targetRenderedDoc(this.slideDoc)}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
styleProvider={this.styleProvider}
hideLinkButton={true}
ScreenToLocalTransform={Transform.Identity}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
docViewPath={returnEmptyDoclist}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
- rootSelected={returnTrue}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
addDocument={returnFalse}
removeDocument={returnFalse}
fitContentsToBox={returnTrue}
- moveDocument={this.props.moveDocument!}
+ moveDocument={this._props.moveDocument!}
focus={emptyFunction}
whenChildContentsActiveChanged={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
bringToFront={returnFalse}
/>
- {/* <div className="presItem-embeddedMask" /> */}
</div>
);
}
@@ -158,26 +166,22 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get transition() {
let transitionInS: number;
- if (this.rootDoc.presentation_transition) transitionInS = NumCast(this.rootDoc.presentation_transition) / 1000;
+ if (this.slideDoc.presentation_transition) transitionInS = NumCast(this.slideDoc.presentation_transition) / 1000;
else transitionInS = 0.5;
- return this.rootDoc.presentation_movement === PresMovement.Jump || this.rootDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
+ return this.slideDoc.presentation_movement === PresMovement.Jump || this.slideDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
}
- private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _titleRef: React.RefObject<EditableView> = React.createRef();
-
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
const element = e.target as any;
e.stopPropagation();
e.preventDefault();
if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
- this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+ 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.rootDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
});
}
@@ -188,12 +192,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
startDrag = (e: PointerEvent) => {
const miniView: boolean = this.toolbarWidth <= 100;
- const activeItem = this.rootDoc;
+ const activeItem = this.slideDoc;
const dragArray = this.presBoxView?._dragArray ?? [];
const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
- if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
- dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
- dragData.moveDocument = this.props.moveDocument;
+ if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
+ dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+ dragData.moveDocument = this._props.moveDocument;
const dragItem: HTMLElement[] = [];
const classesToRestore = new Map<HTMLElement, string>();
if (dragArray.length === 1) {
@@ -237,7 +241,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
onPointerMove = (e: PointerEvent) => {
- const slide = this._itemRef.current!;
+ const slide = this._itemRef.current;
const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
if (slide && dragIsPresItem) {
const rect = slide.getBoundingClientRect();
@@ -256,35 +260,42 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
onPointerLeave = (e: any) => {
- this._itemRef.current!.style.borderTop = '0px';
- this._itemRef.current!.style.borderBottom = '0px';
+ const slide = this._itemRef.current;
+ if (slide) {
+ slide.style.borderTop = '0px';
+ slide.style.borderBottom = '0px';
+ }
document.removeEventListener('pointermove', this.onPointerMove);
};
@action
toggleProperties = () => {
- if (SettingsManager.propertiesWidth < 5) {
- action(() => (SettingsManager.propertiesWidth = 250));
+ if (SettingsManager.Instance.propertiesWidth < 5) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
};
- removePresentationItem = undoable((e: React.MouseEvent) => {
- e.stopPropagation();
- if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
- runInAction(() => (this.presBox!.itemIndex = (this.presBoxView?.itemIndex || 0) - 1));
- }
- this.props.removeDocument?.(this.rootDoc);
- this.presBoxView?.removeFromSelectedArray(this.rootDoc);
- this.removeAllRecordingInOverlay();
- }, 'Remove doc from pres trail');
-
- // set the value/title of the individual pres element
- @undoBatch
- @action
- onSetValue = (value: string) => {
- this.rootDoc.title = !value.trim().length ? '-untitled-' : value;
- return true;
- };
+ removePresentationItem = undoable(
+ action((e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
+ this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
+ }
+ this._props.removeDocument?.(this.slideDoc);
+ this.presBoxView?.removeFromSelectedArray(this.slideDoc);
+ this.removeAllRecordingInOverlay();
+ }),
+ 'Remove doc from pres trail'
+ );
+
+ // set title of the individual pres slide
+ onSetValue = undoable(
+ action((value: string) => {
+ this.slideDoc.title = !value.trim().length ? '-untitled-' : value;
+ return true;
+ }),
+ 'set title of pres element'
+ );
/**
* Method called for updating the view of the currently selected document
@@ -293,7 +304,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param activeItem
*/
@undoBatch
- @action
updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
activeItem.config_x = NumCast(targetDoc.x);
@@ -305,128 +315,99 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// activeItem.config_pinLayout = true;
};
- //wait i dont think i have to do anything here since by default it'll revert to the previously saved if I don't save
- //so basically, don't have an onClick for this, just let it do nada for now
- @undoBatch
- @action
- revertToPreviouslySaved = (presTargetDoc: Doc, activeItem: Doc) => {
- const target = DocCast(activeItem.annotationOn) ?? activeItem;
- PresBox.reversePin(activeItem, target);
- };
-
/**
* Method called for updating the view of the currently selected document
*
* @param presTargetDoc
* @param activeItem
*/
- @undoBatch
- @action
- updateCapturedViewContents = (presTargetDoc: Doc, activeItem: Doc) => {
- const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
- PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
- };
-
- @computed get recordingIsInOverlay() {
- return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc);
- }
+ updateCapturedViewContents = undoable(
+ action((presTargetDoc: Doc, activeItem: Doc) => {
+ const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
+ PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
+ }),
+ 'updated captured view contents'
+ );
// a previously recorded video will have timecode defined
- static videoIsRecorded = (activeItem: Opt<Doc>) => {
- const casted = Cast(activeItem?.recording, Doc, null);
- return casted && 'layout_currentTimecode' in casted;
- };
+ static videoIsRecorded = (activeItem: Opt<Doc>) => 'layout_currentTimecode' in (DocCast(activeItem?.recording) ?? {});
- removeAllRecordingInOverlay = () => {
- Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
- };
+ removeAllRecordingInOverlay = () => Doc.MyOverlayDocs.filter(doc => doc.slides === this.slideDoc).forEach(Doc.RemFromMyOverlay);
+ /// remove all videos that have been recorded from overlay (leave videso that are being recorded to avoid losing data)
static removeEveryExistingRecordingInOverlay = () => {
- // Remove every recording that already exists in overlay view
- Doc.MyOverlayDocs.forEach(doc => {
- if (doc.slides !== null) {
- // if it's a recording video, don't remove from overlay (user can lose data)
- if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) {
- Doc.RemFromMyOverlay(doc);
- }
- }
- });
- };
-
- @undoBatch
- @action
- hideRecording = (e: React.MouseEvent, iconClick: boolean = false) => {
- e.stopPropagation();
- this.removeAllRecordingInOverlay();
- // if (iconClick) PresElementBox.showVideo = false;
- };
-
- @undoBatch
- @action
- showRecording = (activeItem: Doc, iconClick: boolean = false) => {
- // remove the overlays on switch *IF* not opened from the specific icon
- if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
-
- if (activeItem.recording) {
- Doc.AddToMyOverlay(DocCast(activeItem.recording));
- }
+ Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresElementBox.videoIsRecorded(DocCast(doc.slides))) //
+ .forEach(Doc.RemFromMyOverlay);
};
- @undoBatch
- @action
- startRecording = (e: React.MouseEvent, activeItem: Doc) => {
- e.stopPropagation();
- if (PresElementBox.videoIsRecorded(activeItem)) {
- // if we already have an existing recording
- this.showRecording(activeItem, true);
- // // if we already have an existing recording
- // Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null));
- } else {
- // Remove every recording that already exists in overlay view
- // this is a design decision to clear to focus in on the recoding mode
- PresElementBox.removeEveryExistingRecordingInOverlay();
-
- // if we dont have any recording
- const recording = Docs.Create.WebCamDocument('', {
- _width: 384,
- _height: 216,
- layout_hideDocumentButtonBar: true,
- layout_hideDecorationTitle: true,
- layout_hideOpenButton: true,
- // hideDeleteButton: true,
- cloneFieldFilter: new List<string>(['isSystem']),
- });
-
- // attach the recording to the slide, and attach the slide to the recording
- recording.slides = activeItem;
- activeItem.recording = recording;
-
- // make recording box appear in the bottom right corner of the screen
- recording.overlayX = window.innerWidth - NumCast(recording._width) - 20;
- recording.overlayY = window.innerHeight - NumCast(recording._height) - 20;
- Doc.AddToMyOverlay(recording);
- }
- };
+ hideRecording = undoable(
+ action((e: React.MouseEvent, iconClick: boolean = false) => {
+ e.stopPropagation();
+ this.removeAllRecordingInOverlay();
+ }),
+ 'hide video recording'
+ );
+
+ showRecording = undoable(
+ action((activeItem: Doc, iconClick: boolean = false) => {
+ // remove the overlays on switch *IF* not opened from the specific icon
+ if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
+
+ activeItem.recording && Doc.AddToMyOverlay(DocCast(activeItem.recording));
+ }),
+ 'show video recording'
+ );
+
+ startRecording = undoable(
+ action((e: React.MouseEvent, activeItem: Doc) => {
+ e.stopPropagation();
+ if (PresElementBox.videoIsRecorded(activeItem)) {
+ // if we already have an existing recording
+ this.showRecording(activeItem, true);
+ // // if we already have an existing recording
+ // Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null));
+ } else {
+ // we dont have any recording
+ // Remove every recording that already exists in overlay view
+ // this is a design decision to clear to focus in on the recoding mode
+ PresElementBox.removeEveryExistingRecordingInOverlay();
+
+ // create and add a recording to the slide
+ // make recording box appear in the bottom right corner of the screen
+ Doc.AddToMyOverlay(
+ (activeItem.recording = Docs.Create.WebCamDocument('', {
+ _width: 384,
+ _height: 216,
+ overlayX: window.innerWidth - 384 - 20,
+ overlayY: window.innerHeight - 216 - 20,
+ layout_hideDocumentButtonBar: true,
+ layout_hideDecorationTitle: true,
+ layout_hideOpenButton: true,
+ cloneFieldFilter: new List<string>(['isSystem']),
+ slides: activeItem, // attach the slide to the recording
+ }))
+ );
+ }
+ }),
+ 'start video recording'
+ );
@undoBatch
- @action
lfg = (e: React.MouseEvent) => {
e.stopPropagation();
// TODO: fix this bug
- const { toggleChildrenRun } = this.rootDoc;
- TreeView.ToggleChildrenRun.get(this.rootDoc)?.();
+ const { toggleChildrenRun } = this.slideDoc;
+ TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
- // call this.rootDoc.recurChildren() to get all the children
+ // call this.slideDoc.recurChildren() to get all the children
// if (iconClick) PresElementBox.showVideo = false;
};
@computed
get toolbarWidth(): number {
const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
- let width: number = NumCast(this.presBox?._width);
- if (presBoxDocView) width = presBoxDocView.props.PanelWidth();
- if (width === 0) width = 300;
- return width;
+ const width = NumCast(this.presBox?._width);
+ return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
}
@computed get presButtons() {
@@ -434,8 +415,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presBoxColor = StrCast(presBox?._backgroundColor);
const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
const targetDoc = this.targetDoc;
- const activeItem = this.rootDoc;
- const hasChildren = BoolCast(this.rootDoc?.hasChildren);
+ const activeItem = this.slideDoc;
+ const hasChildren = BoolCast(this.slideDoc?.hasChildren);
const items: JSX.Element[] = [];
@@ -460,9 +441,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
);
items.push(
- <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
- <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
- <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
+ <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 }}>
+ <FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
@@ -475,8 +456,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
{!activeItem.presentation_groupWithUp
? 'Not grouped with previous slide (click to group)'
: activeItem.presentation_groupWithUp === 1
- ? 'Run simultaneously with previous slide (click again to run after)'
- : 'Run after previous slide (click to ungroup from previous)'}
+ ? 'Run simultaneously with previous slide (click again to run after)'
+ : 'Run after previous slide (click to ungroup from previous)'}
</div>
}>
<div
@@ -498,14 +479,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
items.push(
- <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+ <Tooltip key="eye" title={<div className="dash-tooltip">{this.slideDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
<div
className="slideButton"
onClick={e => {
e.stopPropagation();
this.presExpandDocumentClick();
}}>
- <FontAwesomeIcon icon={this.rootDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon={this.slideDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
@@ -536,30 +517,30 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get mainItem() {
- const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
+ const isSelected: boolean = this.selectedArray?.has(this.slideDoc) ? true : false;
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.rootDoc;
+ const activeItem: Doc = this.slideDoc;
return (
<div
- className='presItem-container'
- key={this.props.Document[Id] + this.indexInPres}
+ className="presItem-container"
+ key={this.slideDoc[Id] + this.indexInPres}
ref={this._itemRef}
style={{
backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
opacity: this._dragging ? 0.3 : 1,
- paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
- paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
- paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
- paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
+ paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+ paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+ paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+ paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
}}
onDoubleClick={action(e => {
this.toggleProperties();
- this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
+ this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, false);
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
@@ -571,10 +552,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
) : (
<div
ref={this._dragRef}
- className={`presItem-slide ${isCurrent ? 'active' : ''}${this.rootDoc.runProcess ? ' testingv2' : ''}`}
+ className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
style={{
display: 'infline-block',
- backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ 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,
border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
}}>
@@ -583,7 +564,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
display: 'inline-flex',
pointerEvents: isSelected ? undefined : 'none',
- width: `calc(100% ${this.rootDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+ width: `calc(100% ${this.slideDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
cursor: isSelected ? 'text' : 'grab',
}}>
<div
@@ -611,6 +592,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
render() {
- return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
+ return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
}
}
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d68859e88..d0688c338 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,12 +1,12 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { ColorState } from 'react-color';
+import * as React from 'react';
+import { ColorResult } from 'react-color';
+import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
-import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
@@ -23,6 +23,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private _commentRef = React.createRef<HTMLDivElement>();
private _cropRef = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ AnchorMenu.Instance = this;
+ AnchorMenu.Instance._canFade = false;
+ }
+
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable public Status: 'marquee' | 'annotation' | '' = '';
@@ -50,20 +57,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return this._left > 0;
}
- constructor(props: Readonly<{}>) {
- super(props);
-
- AnchorMenu.Instance = this;
- AnchorMenu.Instance._canFade = false;
- }
-
componentWillUnmount() {
this._disposer?.();
}
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views().slice(),
+ () => SelectionManager.Views.slice(),
sel => AnchorMenu.Instance.fadeOut(true)
);
}
@@ -139,13 +139,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action changeHighlightColor = (color: string) => {
- const col: ColorState = {
+ const col: ColorResult = {
hex: color,
- hsl: { a: 0, h: 0, s: 0, l: 0, source: '' },
- hsv: { a: 0, h: 0, s: 0, v: 0, source: '' },
- rgb: { a: 0, r: 0, b: 0, g: 0, source: '' },
- oldHue: 0,
- source: '',
+ hsl: { a: 0, h: 0, s: 0, l: 0 },
+ rgb: { a: 0, r: 0, b: 0, g: 0 },
};
this.highlightColor = Utils.colorString(col);
};
@@ -155,7 +152,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* all selected text available to summarize but its only supported for pdf and web ATM.
* @returns Whether the GPT icon for summarization should appear
*/
- canSummarize = () => SelectionManager.Docs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
+ canSummarize = () => SelectionManager.Docs.some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
render() {
const buttons =
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index e1e87763c..cf19ff6bc 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index d830ba8a9..db21d9a3d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,10 +1,10 @@
-import React = require('react');
+import * as React from 'react';
import './GPTPopup.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import ReactLoading from 'react-loading';
-import Typist from 'react-typist';
+import { TypeAnimation } from 'react-type-animation';
import { Doc } from '../../../../fields/Doc';
import { DocUtils, Docs } from '../../../documents/Documents';
import { Button, IconButton, Type } from 'browndash-components';
@@ -14,6 +14,7 @@ import { AnchorMenu } from '../AnchorMenu';
import { gptImageCall } from '../../../apis/gpt/GPT';
import { Networking } from '../../../Network';
import { Utils } from '../../../../Utils';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
export enum GPTPopupMode {
SUMMARY,
@@ -24,7 +25,7 @@ export enum GPTPopupMode {
interface GPTPopupProps {}
@observer
-export class GPTPopup extends React.Component<GPTPopupProps> {
+export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
static Instance: GPTPopup;
@observable
@@ -177,6 +178,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
constructor(props: GPTPopupProps) {
super(props);
+ makeObservable(this);
GPTPopup.Instance = this;
}
@@ -243,17 +245,18 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
<div className="content-wrapper">
{!this.loading &&
(!this.done ? (
- <Typist
- key={this.text}
- avgTypingDelay={15}
- cursor={{ hideWhenDone: true }}
- onTypingDone={() => {
- setTimeout(() => {
- this.setDone(true);
- }, 500);
- }}>
- {this.text}
- </Typist>
+ <TypeAnimation
+ speed={50}
+ sequence={[
+ this.text,
+ () => {
+ setTimeout(() => {
+ this.setDone(true);
+ }, 500);
+ },
+ ]}
+ //cursor={{ hideWhenDone: true }}
+ />
) : (
this.text
))}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ca9bf7bd2..e342c25b3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,7 +1,9 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
+import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
+import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Height } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -17,24 +19,21 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProvider';
import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
-import React = require('react');
-const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
-const pdfjsLib = require('pdfjs-dist');
const _global = (window /* browser */ || global) /* node */ as any;
//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
-pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.269/build/pdf.worker.mjs';
interface IViewerProps extends FieldViewProps {
Document: Doc;
- rootDoc: Doc;
dataDoc: Doc;
layoutDoc: Doc;
fieldKey: string;
@@ -51,13 +50,19 @@ interface IViewerProps extends FieldViewProps {
* Handles rendering and virtualization of the pdf
*/
@observer
-export class PDFViewer extends React.Component<IViewerProps> {
+export class PDFViewer extends ObservableReactComponent<IViewerProps> {
static _annotationStyle: any = addStyleSheet();
- @observable private _pageSizes: { width: number; height: number }[] = [];
- @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _textSelecting = true;
- @observable private _showWaiting = true;
- @observable private Index: number = -1;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @observable _pageSizes: { width: number; height: number }[] = [];
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @observable _textSelecting = true;
+ @observable _showWaiting = true;
+ @observable Index: number = -1;
private _pdfViewer: any;
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
@@ -85,38 +90,38 @@ export class PDFViewer extends React.Component<IViewerProps> {
@observable isAnnotating = false;
// key where data is stored
@computed get allAnnotations() {
- return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
+ return DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this._props.childFilters(), this._props.childFiltersByRanges());
}
@computed get inlineTextAnnotations() {
return this.allAnnotations.filter(a => a.text_inlineAnnotations);
}
- componentDidMount = async () => {
+ componentDidMount() {
runInAction(() => (this._showWaiting = true));
this.setupPdfJsViewer();
this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
this._disposers.layout_autoHeight = reaction(
- () => this.props.layoutDoc._layout_autoHeight,
+ () => this._props.layoutDoc._layout_autoHeight,
layout_autoHeight => {
if (layout_autoHeight) {
- this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
- this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
+ this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
+ this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
}
);
this._disposers.selected = reaction(
- () => this.props.isSelected(),
- selected => SelectionManager.Views().length === 1 && this.setupPdfJsViewer(),
+ () => this._props.isSelected(),
+ selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
- () => Cast(this.props.Document._layout_curPage, 'number', null),
+ () => Cast(this._props.Document._layout_curPage, 'number', null),
page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
- };
+ }
componentWillUnmount = () => {
Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -124,7 +129,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
};
copy = (e: ClipboardEvent) => {
- if (this.props.isContentActive() && e.clipboardData) {
+ if (this._props.isContentActive() && e.clipboardData) {
e.clipboardData.setData('text/plain', this._selectionText);
const anchor = this._getAnchor(undefined, false);
if (anchor) {
@@ -140,18 +145,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
+ this._pageSizes = Array<{ width: number; height: number }>(this._props.pdf.numPages);
await Promise.all(
this._pageSizes.map((val, i) =>
- this.props.pdf.getPage(i + 1).then(
+ this._props.pdf.getPage(i + 1).then(
action((page: Pdfjs.PDFPageProxy) => {
const page0or180 = page.rotate === 0 || page.rotate === 180;
this._pageSizes.splice(i, 1, {
width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
});
- if (i === this.props.pdf.numPages - 1) {
- this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
+ if (i === this._props.pdf.numPages - 1) {
+ this._props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this._props.pdf.numPages);
}
})
)
@@ -168,27 +173,27 @@ export class PDFViewer extends React.Component<IViewerProps> {
scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
- if (doc !== this.props.rootDoc && 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);
- if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._layout_scrollTop) {
+ 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);
+ 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);
else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
}
} else {
- this._initialScroll = { loc: NumCast(this.props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
+ this._initialScroll = { loc: NumCast(this._props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
}
return focusSpeed;
};
- crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
+ crop = (region: Doc | undefined, addCrop?: boolean) => this._props.crop(region, addCrop);
@action
setupPdfJsViewer = async () => {
if (this._viewerIsSetup) return;
this._viewerIsSetup = true;
this._showWaiting = true;
- this.props.setPdfViewer(this);
+ this._props.setPdfViewer(this);
await this.initialLoad();
this.createPdfViewer();
@@ -196,22 +201,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._freeform_scale = 1));
- this.gotoPage(NumCast(this.props.Document._layout_curPage, 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 };
this._disposers.scale = reaction(
- () => NumCast(this.props.layoutDoc._freeform_scale, 1),
+ () => NumCast(this._props.layoutDoc._freeform_scale, 1),
scale => (this._pdfViewer.currentScaleValue = scale),
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
- () => Math.abs(NumCast(this.props.Document._layout_scrollTop)),
+ () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
pos => {
if (!this._ignoreScroll) {
this._showWaiting && this.setupPdfJsViewer();
- const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition);
+ const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -252,7 +257,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
document.removeEventListener('copy', this.copy);
document.addEventListener('copy', this.copy);
- const eventBus = new PDFJSViewer.EventBus(true);
+ const eventBus = new PDFJSViewer.EventBus();
eventBus._on('pagesinit', this.pagesinit);
eventBus._on(
'pagerendered',
@@ -262,15 +267,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
container: this._mainCont.current,
- viewer: this._viewer.current,
+ viewer: this._viewer.current || undefined,
linkService: pdfLinkService,
findController: pdfFindController,
- renderer: 'canvas',
eventBus,
});
pdfLinkService.setViewer(this._pdfViewer);
- pdfLinkService.setDocument(this.props.pdf, null);
- this._pdfViewer.setDocument(this.props.pdf);
+ pdfLinkService.setDocument(this._props.pdf, null);
+ this._pdfViewer.setDocument(this._props.pdf);
}
@action
@@ -303,13 +307,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
onScroll = (e: React.UIEvent<HTMLElement>) => {
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 (!LinkDocPreview.LinkInfo) {
- this.props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
+ if (!LinkInfo.Instance?.LinkInfo) {
+ this._props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
}
this._ignoreScroll = false;
if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
this._scrollTimer = setTimeout(() => {
- DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
+ DocUtils.MakeLinkToActiveAudio(() => this._props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
this._scrollTimer = undefined;
}, 200);
}
@@ -359,12 +363,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
- if ((this.props.Document._freeform_scale || 1) !== 1) return;
- if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this.props.Document);
+ if ((this._props.Document._freeform_scale || 1) !== 1) return;
+ if ((e.button !== 0 || e.altKey) && this._props.isContentActive(true)) {
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
- this.props.select(false);
+ if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ this._props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
this.isAnnotating = true;
@@ -394,7 +398,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
clearStyleSheetRules(PDFViewer._annotationStyle);
- this.props.select(false);
+ this._props.select(false);
document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
@@ -409,21 +413,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
// Changing which document to add the annotation to (the currently selected PDF)
GPTPopup.Instance.setSidebarId('data_sidebar');
- GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
+ GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
};
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
if (this._mainCont.current) {
- this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
+ this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
const boundingRect = this._mainCont.current.getBoundingClientRect();
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
+ if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this._props.ScreenToLocalTransform().Scale) {
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
- const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
+ const pdfScale = NumCast(this._props.layoutDoc._freeform_scale, 1);
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
// transforms the positions from screen onto the pdf div
@@ -452,7 +456,7 @@ export class PDFViewer extends React.Component<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) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+ 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
};
@@ -461,48 +465,48 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this.props.isContentActive(true)) {
+ if (this._props.isContentActive(true)) {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
- this.props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
+ this._props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
}
}
};
pointerEvents = () =>
- this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
+ this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
? 'all' //
: 'none';
@computed get annotationLayer() {
const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
return (
- <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+ <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 => (
- <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
}
getScrollHeight = () => this._scrollHeight;
- scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
- 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.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+ scrollXf = () => (this._mainCont.current ? this._props.ScreenToLocalTransform().translate(0, NumCast(this._props.layoutDoc._layout_scrollTop)) : this._props.ScreenToLocalTransform());
+ 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])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
+ if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
return isInk ? 'visiblePainted' : 'all';
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
- childPointerEvents = () => (this.props.isContentActive() !== false ? 'all' : 'none');
+ childPointerEvents = () => (this._props.isContentActive() !== false ? 'all' : 'none');
renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
<div
className="pdfViewerDash-overlay"
@@ -512,15 +516,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
}}>
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
setContentView={emptyFunction} // override setContentView to do nothing
- pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+ 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}
+ renderDepth={this._props.renderDepth + 1}
isAnnotationOverlay={true}
- fieldKey={this.props.fieldKey + '_annotations'}
+ fieldKey={this._props.fieldKey + '_annotations'}
getScrollHeight={this.getScrollHeight}
setPreviewCursor={this.setPreviewCursor}
PanelHeight={this.panelHeight}
@@ -536,39 +540,39 @@ export class PDFViewer extends React.Component<IViewerProps> {
</div>
);
@computed get overlayTransparentAnnotations() {
- const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []);
- return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this.props.isContentActive() ? 'none' : undefined);
+ const transparentChildren = DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this.transparentFilter(), []);
+ return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.CanEmbed && this._props.isContentActive() ? 'none' : undefined);
}
@computed get overlayOpaqueAnnotations() {
return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
}
@computed get overlayLayer() {
return (
- <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+ <div style={{ pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : 'none' }}>
{this.overlayTransparentAnnotations}
{this.overlayOpaqueAnnotations}
</div>
);
}
@computed get pdfViewerDiv() {
- return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
+ return <div className={'pdfViewerDash-text' + (this._props.pointerEvents?.() !== 'none' && this._textSelecting && this._props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
}
savedAnnotations = () => this._savedAnnotations;
- addDocumentWrapper = (doc: Doc | Doc[]) => this.props.addDocument!(doc);
+ addDocumentWrapper = (doc: Doc | Doc[]) => this._props.addDocument!(doc);
render() {
TraceMobx();
return (
<div className="pdfViewer-content">
<div
- className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
+ className={`pdfViewerDash${this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
ref={this._mainCont}
onScroll={this.onScroll}
onWheel={this.onZoomWheel}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
style={{
- overflowX: NumCast(this.props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
- height: !this.props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
+ overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
+ height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
}}>
{this.pdfViewerDiv}
{this.annotationLayer}
@@ -577,13 +581,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
- rootDoc={this.props.rootDoc}
+ Document={this._props.Document}
getPageFromScroll={this.getPageFromScroll}
- anchorMenuClick={this.props.anchorMenuClick}
+ anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
- annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+ annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
- docView={this.props.DocumentView!}
+ docView={this._props.DocumentView!}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
selectionText={this.selectionText}
diff --git a/src/client/views/search/CheckBox.scss b/src/client/views/search/CheckBox.scss
deleted file mode 100644
index 2a0085ade..000000000
--- a/src/client/views/search/CheckBox.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-@import "../global/globalCssVariables";
-
-.checkboxfilter {
- display: flex;
- margin-top: 0px;
- padding: 3px;
-
- .outer {
- display: flex;
- position: relative;
- justify-content: center;
- align-items: center;
- margin-top: 0px;
-
- .check-container:hover~.check-box {
- background-color: $medium-blue;
- }
-
- .check-container {
- width: 40px;
- height: 40px;
- position: absolute;
- z-index: 1000;
-
- .checkmark {
- z-index: 1000;
- position: absolute;
- fill-opacity: 0;
- stroke-width: 4px;
- // stroke: white;
- stroke: gray;
- }
- }
-
- .check-box {
- z-index: 900;
- position: relative;
- height: 20px;
- width: 20px;
- overflow: visible;
- background-color: transparent;
- border-style: solid;
- border-color: $medium-gray;
- border-width: 2px;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- }
- }
-
- .checkbox-title {
- display: flex;
- align-items: center;
- text-transform: capitalize;
- margin-left: 15px;
- }
-
-} \ No newline at end of file
diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx
deleted file mode 100644
index 0a1e551ec..000000000
--- a/src/client/views/search/CheckBox.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import "./CheckBox.scss";
-
-interface CheckBoxProps {
- originalStatus: boolean;
- updateStatus(newStatus: boolean): void;
- title: string;
- parent: any;
- numCount: number;
- default: boolean;
-}
-
-@observer
-export class CheckBox extends React.Component<CheckBoxProps>{
- // true = checked, false = unchecked
- @observable private _status: boolean;
- // @observable private uncheckTimeline: anime.AnimeTimelineInstance;
- // @observable private checkTimeline: anime.AnimeTimelineInstance;
- @observable private checkRef: any;
- @observable private _resetReaction?: IReactionDisposer;
-
-
- constructor(props: CheckBoxProps) {
- super(props);
- this._status = this.props.originalStatus;
- this.checkRef = React.createRef();
-
- // this.checkTimeline = anime.timeline({
- // loop: false,
- // autoplay: false,
- // direction: "normal",
- // }); this.uncheckTimeline = anime.timeline({
- // loop: false,
- // autoplay: false,
- // direction: "normal",
- // });
- }
-
- // componentDidMount = () => {
- // this.uncheckTimeline.add({
- // targets: this.checkRef.current,
- // easing: "easeInOutQuad",
- // duration: 500,
- // opacity: 0,
- // });
- // this.checkTimeline.add({
- // targets: this.checkRef.current,
- // easing: "easeInOutQuad",
- // duration: 500,
- // strokeDashoffset: [anime.setDashoffset, 0],
- // opacity: 1
- // });
-
- // if (this.props.originalStatus) {
- // this.checkTimeline.play();
- // this.checkTimeline.restart();
- // }
-
- // this._resetReaction = reaction(
- // () => this.props.parent._resetBoolean,
- // () => {
- // if (this.props.parent._resetBoolean) {
- // runInAction(() => {
- // this.reset();
- // this.props.parent._resetCounter++;
- // if (this.props.parent._resetCounter === this.props.numCount) {
- // this.props.parent._resetCounter = 0;
- // this.props.parent._resetBoolean = false;
- // }
- // });
- // }
- // },
- // );
- // }
-
- // @action.bound
- // reset() {
- // if (this.props.default) {
- // if (!this._status) {
- // this._status = true;
- // this.checkTimeline.play();
- // this.checkTimeline.restart();
- // }
- // }
- // else {
- // if (this._status) {
- // this._status = false;
- // this.uncheckTimeline.play();
- // this.uncheckTimeline.restart();
- // }
- // }
-
- // this.props.updateStatus(this.props.default);
- // }
-
- @action.bound
- onClick = () => {
- // if (this._status) {
- // this.uncheckTimeline.play();
- // this.uncheckTimeline.restart();
- // }
- // else {
- // this.checkTimeline.play();
- // this.checkTimeline.restart();
-
- // }
- // this._status = !this._status;
- // this.props.updateStatus(this._status);
-
- }
-
- render() {
- return (
- <div className="checkboxfilter" onClick={this.onClick}>
- <div className="outer">
- <div className="check-container">
- <svg viewBox="0 12 40 40">
- <path ref={this.checkRef} className="checkmark" d="M14.1 27.2l7.1 7.2 16.7-18" />
- </svg>
- </div>
- <div className="check-box" />
- </div>
- <div className="checkbox-title">{this.props.title}</div>
- </div>
- );
- }
-
-} \ No newline at end of file
diff --git a/src/client/views/search/CollectionFilters.scss b/src/client/views/search/CollectionFilters.scss
deleted file mode 100644
index 845b16f67..000000000
--- a/src/client/views/search/CollectionFilters.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-@import "../global/globalCssVariables";
-
-.collection-filters {
- display: flex;
- flex-direction: row;
- width: 100%;
-
- &.main {
- width: 47%;
- float: left;
- }
-
- &.optional {
- width: 60%;
- display: grid;
- grid-template-columns: 50% 50%;
- float: right;
- opacity: 0;
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/CollectionFilters.tsx b/src/client/views/search/CollectionFilters.tsx
deleted file mode 100644
index 48d0b2ddb..000000000
--- a/src/client/views/search/CollectionFilters.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as React from 'react';
-import { observable, action } from 'mobx';
-import { CheckBox } from './CheckBox';
-import "./CollectionFilters.scss";
-import * as anime from 'animejs';
-
-interface CollectionFilterProps {
- collectionStatus: boolean;
- updateCollectionStatus(val: boolean): void;
- collectionSelfStatus: boolean;
- updateSelfCollectionStatus(val: boolean): void;
- collectionParentStatus: boolean;
- updateParentCollectionStatus(val: boolean): void;
-}
-
-export class CollectionFilters extends React.Component<CollectionFilterProps> {
-
- static Instance: CollectionFilters;
-
- @observable public _resetBoolean = false;
- @observable public _resetCounter: number = 0;
-
- @observable private _collectionsSelected = this.props.collectionStatus;
- @observable private _timeline: anime.AnimeTimelineInstance;
- @observable private _ref: any;
-
- constructor(props: CollectionFilterProps) {
- super(props);
- CollectionFilters.Instance = this;
- this._ref = React.createRef();
-
- this._timeline = anime.timeline({
- loop: false,
- autoplay: false,
- direction: "reverse",
- });
- }
-
- componentDidMount = () => {
- this._timeline.add({
- targets: this._ref.current,
- easing: "easeInOutQuad",
- duration: 500,
- opacity: 1,
- });
-
- if (this._collectionsSelected) {
- this._timeline.play();
- this._timeline.reverse();
- }
- }
-
- @action.bound
- resetCollectionFilters() { this._resetBoolean = true; }
-
- @action.bound
- updateColStat(val: boolean) {
- this.props.updateCollectionStatus(val);
-
- if (this._collectionsSelected !== val) {
- this._timeline.play();
- this._timeline.reverse();
- }
-
- this._collectionsSelected = val;
- }
-
- render() {
- return (
- <div>
- <div className="collection-filters">
- <div className="collection-filters main">
- <CheckBox default={false} title={"limit to current collection"} parent={this} numCount={3} updateStatus={this.updateColStat} originalStatus={this.props.collectionStatus} />
- </div>
- <div className="collection-filters optional" ref={this._ref}>
- <CheckBox default={true} title={"Search in self"} parent={this} numCount={3} updateStatus={this.props.updateSelfCollectionStatus} originalStatus={this.props.collectionSelfStatus} />
- <CheckBox default={true} title={"Search in parent"} parent={this} numCount={3} updateStatus={this.props.updateParentCollectionStatus} originalStatus={this.props.collectionParentStatus} />
- </div>
- </div>
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
deleted file mode 100644
index 6aaf7918d..000000000
--- a/src/client/views/search/IconBar.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-@import "../global/globalCssVariables";
-
-.icon-bar {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-evenly;
- height: auto;
- width: 100%;
- flex-direction: row-reverse;
-} \ No newline at end of file
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
deleted file mode 100644
index 540c1b5e1..000000000
--- a/src/client/views/search/IconBar.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
-import './IconBar.scss';
-import { IconButton } from './IconButton';
-import './IconButton.scss';
-
-export interface IconBarProps {
- setIcons: (icons: string[]) => void;
-}
-
-@observer
-export class IconBar extends React.Component<IconBarProps> {
- public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB, DocumentType.MAP];
-
- @observable private _icons: string[] = this._allIcons;
-
- static Instance: IconBar;
-
- @observable public _resetClicked: boolean = false;
- @observable public _selectAllClicked: boolean = false;
- @observable public _reset: number = 0;
- @observable public _select: number = 0;
-
- @action.bound
- updateIcon(newArray: string[]) {
- this._icons = newArray;
- this.props.setIcons?.(this._icons);
- }
-
- @action.bound
- getIcons(): string[] {
- return this._icons;
- }
-
- constructor(props: any) {
- super(props);
- IconBar.Instance = this;
- }
-
- @action.bound
- getList(): string[] {
- return this.getIcons();
- }
-
- @action.bound
- updateList(newList: string[]) {
- this.updateIcon(newList);
- }
-
- @action.bound
- resetSelf = () => {
- this._resetClicked = true;
- this.updateList([]);
- };
-
- @action.bound
- selectAll = () => {
- this._selectAllClicked = true;
- this.updateList(this._allIcons);
- };
-
- render() {
- return (
- <div className="icon-bar">
- {this._allIcons.map((type: string) => (
- <IconButton key={type.toString()} type={type} />
- ))}
- </div>
- );
- }
-}
diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss
deleted file mode 100644
index 3cb08d756..000000000
--- a/src/client/views/search/IconButton.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@import "../global/globalCssVariables";
-
-.type-outer {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 30px;
-
- .type-icon {
- height: 30px;
- width: 30px;
- color: $white;
- // background-color: rgb(194, 194, 197);
- background-color: gray;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- font-size: 2em;
-
- .fontawesome-icon {
- height: 15px;
- width: 15px
- }
- }
-
- .filter-description {
- text-transform: capitalize;
- font-size: 10;
- text-align: center;
- height: 15px;
- margin-top: 5px;
- opacity: 1;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- }
-
- .type-icon:hover {
- transform: scale(1.1);
- background-color: $medium-blue;
- opacity: 1;
-
- +.filter-description {
- opacity: 1;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
deleted file mode 100644
index 6cf3a5f72..000000000
--- a/src/client/views/search/IconButton.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import * as _ from "lodash";
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { DocumentType } from "../../documents/DocumentTypes";
-import '../global/globalCssVariables.scss';
-import { IconBar } from './IconBar';
-import "./IconButton.scss";
-import "./SearchBox.scss";
-import { Font } from '@react-pdf/renderer';
-
-interface IconButtonProps {
- type: string;
-}
-
-@observer
-export class IconButton extends React.Component<IconButtonProps>{
-
- @observable private _isSelected: boolean = IconBar.Instance.getIcons().indexOf(this.props.type) !== -1;
- @observable private _hover = false;
- private _resetReaction?: IReactionDisposer;
- private _selectAllReaction?: IReactionDisposer;
-
- static Instance: IconButton;
- constructor(props: IconButtonProps) {
- super(props);
- IconButton.Instance = this;
- }
-
- componentDidMount = () => {
- this._resetReaction = reaction(
- () => IconBar.Instance._resetClicked,
- action(() => {
- if (IconBar.Instance._resetClicked) {
- this._isSelected = false;
- IconBar.Instance._reset++;
- if (IconBar.Instance._reset === 9) {
- IconBar.Instance._reset = 0;
- IconBar.Instance._resetClicked = false;
- }
- }
- }),
- );
-
- this._selectAllReaction = reaction(
- () => IconBar.Instance._selectAllClicked,
- action(() => {
- if (IconBar.Instance._selectAllClicked) {
- this._isSelected = true;
- IconBar.Instance._select++;
- if (IconBar.Instance._select === 9) {
- IconBar.Instance._select = 0;
- IconBar.Instance._selectAllClicked = false;
- }
- }
- }),
- );
- }
-
- @action.bound
- getIcon() {
- switch (this.props.type) {
- case (DocumentType.NONE): return "ban";
- case (DocumentType.AUDIO): return "music";
- case (DocumentType.COL): return "object-group";
- case (DocumentType.IMG): return "image";
- case (DocumentType.LINK): return "link";
- case (DocumentType.PDF): return "file-pdf";
- case (DocumentType.RTF): return "sticky-note";
- case (DocumentType.VID): return "video";
- case (DocumentType.WEB): return "globe-asia";
- case (DocumentType.MAP): return "map-marker-alt";
- default: return "caret-down";
- }
- }
-
- @action.bound
- onClick = () => {
- const newList: string[] = IconBar.Instance.getIcons();
-
- if (!this._isSelected) {
- this._isSelected = true;
- newList.push(this.props.type);
- }
- else {
- this._isSelected = false;
- _.pull(newList, this.props.type);
- }
-
- IconBar.Instance.updateIcon(newList);
- }
-
- selected = {
- opacity: 1,
- backgroundColor: "#121721",
- //backgroundColor: "rgb(128, 128, 128)"
- };
-
- notSelected = {
- opacity: 0.2,
- backgroundColor: "#121721",
- };
-
- hoverStyle = {
- opacity: 1,
- backgroundColor: "rgb(128, 128, 128)"
- //backgroundColor: "rgb(178, 206, 248)" //$medium-blue
- };
-
- render() {
- return (
- <div className="type-outer" id={this.props.type + "-filter"}
- onMouseEnter={() => this._hover = true}
- onMouseLeave={() => this._hover = false}
- onClick={this.onClick}>
- <div className="type-icon" id={this.props.type + "-icon"}
- style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected}
- >
- <FontAwesomeIcon className="fontawesome-icon" icon={this.getIcon()} />
- </div>
- {/* <div className="filter-description">{this.props.type}</div> */}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/NaviconButton.scss b/src/client/views/search/NaviconButton.scss
deleted file mode 100644
index 8a70b29de..000000000
--- a/src/client/views/search/NaviconButton.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@import "../global/globalCssVariables";
-
-$height-icon: 15px;
-$width-line: 30px;
-$height-line: 4px;
-
-$transition-time: 0.4s;
-$rotation: 45deg;
-$translateY: ($height-icon / 2);
-$translateX: 0;
-
-#hamburger-icon {
- width: $width-line;
- height: $height-icon;
- position: relative;
- display: block;
- transition: all $transition-time;
- -webkit-transition: all $transition-time;
- -moz-transition: all $transition-time;
-
- .line {
- display: block;
- background: $medium-gray;
- width: $width-line;
- height: $height-line;
- position: absolute;
- left: 0;
- border-radius: ($height-line / 2);
- transition: all $transition-time;
- -webkit-transition: all $transition-time;
- -moz-transition: all $transition-time;
-
- &.line-1 {
- top: 0;
- }
-
- &.line-2 {
- top: 50%;
- }
-
- &.line-3 {
- top: 100%;
- }
- }
-}
-
-.filter-header.active {
- .line-1 {
- transform: translateY($translateY) translateX($translateX) rotate($rotation);
- -webkit-transform: translateY($translateY) translateX($translateX) rotate($rotation);
- -moz-transform: translateY($translateY) translateX($translateX) rotate($rotation);
- }
-
- .line-2 {
- opacity: 0;
- }
-
- .line-3 {
- transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
- -webkit-transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
- -moz-transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
- }
-}
-
-.filter-header:hover #hamburger-icon {
- transform: scale(1.1);
- -webkit-transform: scale(1.1);
- -moz-transform: scale(1.1);
-} \ No newline at end of file
diff --git a/src/client/views/search/NaviconButton.tsx b/src/client/views/search/NaviconButton.tsx
deleted file mode 100644
index 0fa4a0fca..000000000
--- a/src/client/views/search/NaviconButton.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import "./NaviconButton.scss";
-import * as $ from 'jquery';
-import { observable } from 'mobx';
-
-export interface NaviconProps {
- onClick(): void;
-}
-
-export class NaviconButton extends React.Component<NaviconProps> {
-
- @observable private _ref: React.RefObject<HTMLAnchorElement> = React.createRef();
-
- componentDidMount = () => {
- const that = this;
- if (this._ref.current) {
- this._ref.current.addEventListener("click", function (e) {
- e.preventDefault();
- if (that._ref.current) {
- that._ref.current.classList.toggle('active');
- return false;
- }
- });
- }
- }
-
- render() {
- return (
- <a id="hamburger-icon" href="#" ref={this._ref} title="Menu">
- <span className="line line-1"></span>
- <span className="line line-2"></span>
- <span className="line line-3"></span>
- </a>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index a439aea3e..09e459f7d 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -1,5 +1,4 @@
-@import "../global/globalCssVariables";
-@import "./NaviconButton.scss";
+@import '../global/globalCssVariables.module.scss';
.searchBox-container {
width: 100%;
@@ -47,7 +46,6 @@
}
.section-header {
-
.section-title {
font-size: $body-text;
font-weight: 600;
@@ -57,7 +55,7 @@
display: flex;
color: $light-gray;
}
-
+
padding: 5px 10px;
display: flex;
flex-direction: column;
@@ -71,7 +69,7 @@
flex-direction: column;
width: 100%;
height: fit-content;
- justify-content: "center";
+ justify-content: 'center';
.searchBox-recommendations-view {
margin-top: 10px;
@@ -81,8 +79,6 @@
flex-direction: column;
gap: 10px;
padding: 0px 10px;
-
-
}
}
@@ -91,8 +87,8 @@
flex-direction: column;
width: 100%;
height: fit-content;
- justify-content: "center";
-
+ justify-content: 'center';
+
.searchBox-results-view {
display: inline-block;
width: 100%;
@@ -147,4 +143,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index aa46e57dc..ccfccb771 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,5 +1,5 @@
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
@@ -61,6 +61,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
*/
constructor(props: any) {
super(props);
+ makeObservable(this);
SearchBox.Instance = this;
}
@@ -186,7 +187,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
@action
searchCollection(query: string) {
this._selectedResult = undefined;
- this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.rootDoc, query);
+ this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.Document, query);
this.computePageRanks();
}
diff --git a/src/client/views/search/SelectorContextMenu.scss b/src/client/views/search/SelectorContextMenu.scss
index a114f679c..097a6107d 100644
--- a/src/client/views/search/SelectorContextMenu.scss
+++ b/src/client/views/search/SelectorContextMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
.parents {
background: $light-blue;
@@ -13,4 +13,4 @@
border-color: $medium-blue;
border-bottom-style: solid;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/search/ToggleBar.scss b/src/client/views/search/ToggleBar.scss
deleted file mode 100644
index 3a164f133..000000000
--- a/src/client/views/search/ToggleBar.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-@import "../global/globalCssVariables";
-
-.toggle-title {
- display: flex;
- align-items: center;
- color: $white;
- text-transform: uppercase;
- flex-direction: row;
- justify-content: space-around;
- padding: 5px;
-
- .toggle-option {
- width: 50%;
- text-align: center;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- color: gray;
- font-size: 13;
- }
-}
-
-.toggle-bar {
- // height: 50px;
- height: 30px;
- width: 100px;
- background-color: $medium-gray;
- border-radius: 10px;
- padding: 5px;
- display: flex;
- align-items: center;
-
- .toggle-button {
- // width: 275px;
- width: 40px;
- height: 100%;
- border-radius: 10px;
- background-color: $white;
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
deleted file mode 100644
index 466822eba..000000000
--- a/src/client/views/search/ToggleBar.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, computed } from 'mobx';
-import "./SearchBox.scss";
-import "./ToggleBar.scss";
-import * as anime from 'animejs';
-
-export interface ToggleBarProps {
- originalStatus: boolean;
- optionOne: string;
- optionTwo: string;
- handleChange(): void;
- getStatus(): boolean;
-}
-
-@observer
-export class ToggleBar extends React.Component<ToggleBarProps>{
- static Instance: ToggleBar;
-
- @observable private _forwardTimeline: anime.AnimeTimelineInstance;
- @observable private _toggleButton: React.RefObject<HTMLDivElement>;
- @observable private _originalStatus: boolean = this.props.originalStatus;
-
- constructor(props: ToggleBarProps) {
- super(props);
- ToggleBar.Instance = this;
- this._toggleButton = React.createRef();
- this._forwardTimeline = anime.timeline({
- loop: false,
- autoplay: false,
- direction: "reverse",
- });
- }
-
- componentDidMount = () => {
- const totalWidth = 265;
-
- if (this._originalStatus) {
- this._forwardTimeline.add({
- targets: this._toggleButton.current,
- translateX: totalWidth,
- easing: "easeInOutQuad",
- duration: 500
- });
- }
- else {
- this._forwardTimeline.add({
- targets: this._toggleButton.current,
- translateX: -totalWidth,
- easing: "easeInOutQuad",
- duration: 500
- });
- }
- }
-
- @action.bound
- onclick() {
- this._forwardTimeline.play();
- this._forwardTimeline.reverse();
- this.props.handleChange();
- }
-
- @action.bound
- public resetToggle = () => {
- if (!this.props.getStatus()) {
- this._forwardTimeline.play();
- this._forwardTimeline.reverse();
- this.props.handleChange();
- }
- }
-
- render() {
- return (
- <div>
- <div className="toggle-title">
- <div className="toggle-option" style={{ opacity: (this.props.getStatus() ? 1 : .4) }}>{this.props.optionOne}</div>
- <div className="toggle-option" style={{ opacity: (this.props.getStatus() ? .4 : 1) }}>{this.props.optionTwo}</div>
- </div>
- <div className="toggle-bar" id="toggle-bar" onClick={this.onclick} style={{ flexDirection: (this._originalStatus ? "row" : "row-reverse") }}>
- <div className="toggle-button" id="toggle-button" ref={this._toggleButton} />
- </div>
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 2139919e0..39e778b76 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,4 +1,4 @@
-import React = require('react');
+import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ListBox } from 'browndash-components';
import { computed } from 'mobx';
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 2237d5ac1..20245104e 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,5 +1,4 @@
-@import '../global/globalCssVariables';
-
+@import '../global/globalCssVariables.module.scss';
.topbar-container {
flex-direction: column;
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 55d94406a..d1c039d95 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -19,7 +19,6 @@ import { SharingManager } from '../../util/SharingManager';
import { Transform } from '../../util/Transform';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { CollectionLinearView } from '../collections/collectionLinear';
-import { ContextMenu } from '../ContextMenu';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
@@ -100,12 +99,10 @@ export class TopBar extends React.Component {
<div className="collectionMenu-contMenuButtons" style={{ height: '100%' }}>
<CollectionLinearView
Document={selDoc}
- DataDoc={undefined}
fieldKey="data"
dropAction="embed"
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
isContentActive={returnTrue}
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
index 249aee9d6..5744ebbcd 100644
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ b/src/client/views/webcam/DashWebRTCVideo.scss
@@ -1,11 +1,11 @@
-@import "../global/globalCssVariables";
+@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;
+ border: solid #bbbbbbbb 5px;
pointer-events: all;
display: flex;
flex-direction: column;
@@ -44,7 +44,7 @@
#roomName {
outline: none;
border-radius: inherit;
- border: 1px solid #BBBBBBBB;
+ border: 1px solid #bbbbbbbb;
margin: 10px;
padding: 10px;
}
@@ -79,5 +79,4 @@
margin: 5px;
border: 1px solid black;
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 524492226..093806127 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -11,7 +11,7 @@ import { DocumentView } from '../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './DashWebRTCVideo.scss';
import { hangup, initialize, refreshVideos } from './WebCamLogic';
-import React = require('react');
+import * as React from 'react';
/**
* This models the component that will be rendered, that can be used as a doc that will reflect the video cams.